今天我们将深入学习Java 反射机制(Reflection)—— 这是 Java 语言的动态特性之一,允许程序在运行时获取类的信息并操作类的成员(属性、方法、构造器等)。在 JavaWeb 开发中,反射是框架设计的灵魂(如 Spring 的 IOC 容器、MyBatis 的 ORM 映射),掌握反射机制能帮助我们理解框架底层原理,编写更灵活的代码。
什么是反射机制?
反射机制是指程序在运行时能够获取自身的信息:
- 可以知道一个类的所有属性和方法
- 可以调用一个对象的任意方法
- 可以修改一个对象的任意属性
- 可以动态创建类的实例
简单来说,反射让 Java 程序在运行时拥有了 “自审” 和 “操作自身” 的能力,突破了编译期的类型限制,实现了动态性。
反射的应用场景:
- 框架开发:Spring、MyBatis 等框架大量使用反射实现配置化操作
- 动态代理:AOP(面向切面编程)的实现基础
- 注解处理:解析自定义注解并执行相应逻辑
- 序列化与反序列化:对象与字节流的转换
- 数据库 ORM 映射:对象与表结构的映射
反射的核心类
Java 反射机制主要通过java.lang.reflect包中的类实现,核心类包括:
Class | 表示类的字节码对象,是反射的入口 |
Constructor | 表示类的构造方法 |
Method | 表示类的方法 |
Field | 表示类的属性 |
Modifier | 提供方法解析类和成员的修饰符(如 public、private) |
这些类共同协作,让我们能够在运行时操作类的各种成员。
反射的入口:Class 对象
Class类是反射的核心,每个类在 JVM 中都有一个对应的Class对象,它包含了该类的所有信息。获取Class对象有三种方式:
方式 1:通过Class.forName()方法(最常用)
try {
// 参数:类的全限定名(包名+类名)
Class<?> clazz = Class.forName("com.example.User");
System.out.println("获取到的Class对象:" + clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
这种方式最灵活,不需要在编译期知道具体类名,可以通过字符串动态加载类(常用于配置文件中指定类名)。
方式 2:通过类名.class属性
// 直接通过类名获取
Class<User> clazz = User.class;
System.out.println("获取到的Class对象:" + clazz);
这种方式需要在编译期知道具体类,编译时会检查类是否存在。
方式 3:通过对象.getClass()方法
User user = new User();
// 通过对象实例获取
Class<? extends User> clazz = user.getClass();
System.out.println("获取到的Class对象:" + clazz);
这种方式需要先有对象实例,适合已有对象的场景。
注意:一个类在 JVM 中只有一个Class对象,无论通过哪种方式获取,都是同一个对象:
Class<?> clazz1 = Class.forName("com.example.User");
Class<User> clazz2 = User.class;
User user = new User();
Class<? extends User> clazz3 = user.getClass();
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
通过反射操作类的构造方法
Constructor类用于表示和操作类的构造方法,通过Class对象可以获取类的所有构造方法,并创建对象实例。
示例:User 类
首先定义一个用于测试的User类:
package com.example;
public class User {
private String username;
private int age;
private String email;
// 无参构造
public User() {
}
// 有参构造1
public User(String username, int age) {
this.username = username;
this.age = age;
}
// 有参构造2(私有)
private User(String username, int age, String email) {
this.username = username;
this.age = age;
this.email = email;
}
// getter和setter方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\\'' +
", age=" + age +
", email='" + email + '\\'' +
'}';
}
}
获取构造方法并创建对象
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ConstructorReflectionDemo {
public static void main(String[] args) {
try {
// 获取User类的Class对象
Class<?> clazz = Class.forName("com.example.User");
// 1. 获取无参构造方法并创建对象
Constructor<?> constructor1 = clazz.getConstructor();
User user1 = (User) constructor1.newInstance(); // 调用无参构造
user1.setUsername("张三");
user1.setAge(20);
System.out.println("无参构造创建的对象:" + user1);
// 2. 获取有参构造方法并创建对象
// 参数:构造方法的参数类型.class
Constructor<?> constructor2 = clazz.getConstructor(String.class, int.class);
User user2 = (User) constructor2.newInstance("李四", 25); // 传递参数
System.out.println("有参构造创建的对象:" + user2);
// 3. 获取私有构造方法并创建对象
// getDeclaredConstructor()可以获取私有构造
Constructor<?> constructor3 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
constructor3.setAccessible(true); // 暴力访问(忽略访问权限修饰符)
User user3 = (User) constructor3.newInstance("王五", 30, "wangwu@example.com");
System.out.println("私有构造创建的对象:" + user3);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
关键方法说明:
- getConstructor(Class<?>… parameterTypes):获取 public 修饰的构造方法
- getDeclaredConstructor(Class<?>… parameterTypes):获取所有构造方法(包括 private)
- newInstance(Object… initargs):调用构造方法创建对象
- setAccessible(true):关闭访问权限检查,用于访问私有成员
通过反射操作类的方法
Method类用于表示和操作类的方法,通过反射可以调用类的任意方法(包括私有方法)。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodReflectionDemo {
public static void main(String[] args) {
try {
// 获取Class对象和实例
Class<?> clazz = Class.forName("com.example.User");
User user = (User) clazz.getConstructor().newInstance();
// 1. 获取并调用public方法(setUsername)
// 参数:方法名,参数类型.class
Method setUsernameMethod = clazz.getMethod("setUsername", String.class);
// 调用方法:invoke(对象, 参数值)
setUsernameMethod.invoke(user, "赵六");
// 获取getUsername方法并调用
Method getUsernameMethod = clazz.getMethod("getUsername");
String username = (String) getUsernameMethod.invoke(user);
System.out.println("用户名:" + username);
// 2. 调用有多个参数的方法
Method setAgeMethod = clazz.getMethod("setAge", int.class);
setAgeMethod.invoke(user, 28);
// 3. 调用私有方法(假设User类有一个私有方法)
// 先在User类中添加一个私有方法:
// private void showInfo() {
// System.out.println("用户名:" + username + ", 年龄:" + age);
// }
Method showInfoMethod = clazz.getDeclaredMethod("showInfo");
showInfoMethod.setAccessible(true); // 暴力访问私有方法
showInfoMethod.invoke(user); // 调用私有方法
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
关键方法说明:
- getMethod(String name, Class<?>… parameterTypes):获取 public 修饰的方法
- getDeclaredMethod(String name, Class<?>… parameterTypes):获取所有方法(包括 private)
- invoke(Object obj, Object… args):调用方法,第一个参数是对象实例(静态方法可以为 null)
- setAccessible(true):同样适用于方法,用于访问私有方法
通过反射操作类的属性
Field类用于表示和操作类的属性,通过反射可以获取和修改类的任意属性(包括私有属性)。
import java.lang.reflect.Field;
public class FieldReflectionDemo {
public static void main(String[] args) {
try {
// 创建User对象
User user = new User("孙七", 35);
Class<?> clazz = user.getClass();
// 1. 获取并修改public属性(如果有的话)
// 假设User有public属性:public String address;
// Field addressField = clazz.getField("address");
// addressField.set(user, "北京市"); // 设置属性值
// String address = (String) addressField.get(user); // 获取属性值
// 2. 获取并修改private属性(username)
Field usernameField = clazz.getDeclaredField("username");
usernameField.setAccessible(true); // 暴力访问私有属性
// 获取属性值
String username = (String) usernameField.get(user);
System.out.println("修改前的用户名:" + username);
// 修改属性值
usernameField.set(user, "孙七_修改后");
System.out.println("修改后的用户名:" + user.getUsername());
// 3. 获取并修改private属性(age)
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(user, 36); // 修改年龄
System.out.println("修改后的年龄:" + user.getAge());
// 4. 获取静态属性(如果有的话)
// 假设User有静态属性:public static String version;
// Field versionField = clazz.getField("version");
// versionField.set(null, "1.0.0"); // 静态属性,第一个参数为null
// String version = (String) versionField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
关键方法说明:
- getField(String name):获取 public 修饰的属性
- getDeclaredField(String name):获取所有属性(包括 private)
- get(Object obj):获取属性值
- set(Object obj, Object value):设置属性值(静态属性第一个参数为 null)
- setAccessible(true):用于访问私有属性
反射与注解结合
注解(Annotation)是 Java 5 + 引入的特性,与反射结合可以实现强大的功能(如 Spring 的@Autowired、MyBatis 的@Select)。
示例:自定义注解与反射解析
import java.lang.annotation.*;
import java.lang.reflect.Field;
// 1. 定义自定义注解
@Target(ElementType.FIELD) // 注解作用在属性上
@Retention(RetentionPolicy.RUNTIME) // 注解保留到运行时(反射可见)
public @interface Column {
String name(); // 数据库列名
boolean nullable() default true; // 是否允许为null
}
// 2. 使用注解的实体类
class User {
@Column(name = "user_name", nullable = false)
private String username;
@Column(name = "user_age")
private int age;
private String email; // 没有注解
// 构造方法、getter、setter省略
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
// 3. 通过反射解析注解
public class AnnotationReflectionDemo {
public static void main(String[] args) {
try {
Class<?> clazz = User.class;
// 获取所有属性
Field[] fields = clazz.getDeclaredFields();
System.out.println("解析User类的注解信息:");
for (Field field : fields) {
// 判断属性是否有@Column注解
if (field.isAnnotationPresent(Column.class)) {
// 获取注解实例
Column column = field.getAnnotation(Column.class);
System.out.println("属性名:" + field.getName());
System.out.println("数据库列名:" + column.name());
System.out.println("是否允许为null:" + column.nullable());
System.out.println("———————");
} else {
System.out.println("属性名:" + field.getName() + "(无@Column注解)");
System.out.println("———————");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
解析User类的注解信息:
属性名:username
数据库列名:user_name
是否允许为null:false
———————
属性名:age
数据库列名:user_age
是否允许为null:true
———————
属性名:email(无@Column注解)
———————
这种方式是 ORM 框架(如 MyBatis)实现对象与数据库表映射的核心原理:通过注解标记属性与列的映射关系,再通过反射解析注解生成 SQL 语句。
反射的优缺点
优点
缺点
优化反射性能的方法:
- 缓存Class、Method、Field等对象,避免频繁获取
- 尽量使用setAccessible(true)关闭访问检查(可提升性能)
- 非必要场景避免使用反射
反射在 JavaWeb 中的应用
反射是 JavaWeb 开发的核心技术之一,以下是几个典型应用场景:
1. Servlet 容器初始化
Tomcat 等 Servlet 容器通过反射初始化 Servlet:
// Tomcat内部工作原理简化
public class ServletContainer {
public void initServlet(String servletClassName) {
try {
// 1. 通过类名加载Servlet类
Class<?> servletClass = Class.forName(servletClassName);
// 2. 实例化Servlet
Object servlet = servletClass.getConstructor().newInstance();
// 3. 调用init方法
Method initMethod = servletClass.getMethod("init", ServletConfig.class);
initMethod.invoke(servlet, new MyServletConfig());
System.out.println("Servlet初始化完成:" + servletClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. Spring IOC 容器
Spring 的 IOC(控制反转)容器通过反射创建和管理 Bean:
<!– Spring配置文件 –>
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
Spring 容器解析配置文件,通过反射创建UserService实例,并调用setUserDao方法注入依赖。
3. 自定义 MVC 框架
简单 MVC 框架通过反射处理请求:
// 简化的控制器
@Controller
public class UserController {
@RequestMapping("/user/list")
public String listUsers() {
// 处理用户列表请求
return "userList";
}
}
// 框架通过反射解析@RequestMapping注解
public class DispatcherServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
String path = request.getRequestURI(); // 获取请求路径
// 扫描所有@Controller类
for (Class<?> clazz : controllerClasses) {
// 解析@RequestMapping注解,找到匹配的方法
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
if (mapping.value().equals(path)) {
// 反射调用方法处理请求
Object controller = clazz.getConstructor().newInstance();
String view = (String) method.invoke(controller);
// 转发到视图
request.getRequestDispatcher(view + ".jsp").forward(request, response);
return;
}
}
}
}
}
}
总结与实践
知识点回顾
- 创建对象:通过Constructor.newInstance()
- 调用方法:通过Method.invoke()
- 操作属性:通过Field.get()和Field.set()
实践任务
简易 ORM 工具:
- 创建@Table注解(标记类与数据库表的映射)
- 创建@Column注解(标记属性与表字段的映射)
- 编写SqlGenerator类,通过反射解析带注解的实体类
- 生成插入数据的 SQL 语句(如INSERT INTO user (user_name, age) VALUES (?, ?))
动态代理实现:
- 创建UserService接口和UserServiceImpl实现类
- 使用InvocationHandler和反射实现动态代理
- 在代理中添加日志记录功能(调用方法前打印 “开始执行”,调用后打印 “执行结束”)
评论前必须登录!
注册