1. 反射的本质

反射(Reflection)是 Java 语言提供的一种动态机制,允许程序在运行时检查或修改类、接口、字段、方法等的结构信息,而无需在编译时知道这些类的具体实现。

几乎所有主流 Java 框架(Spring、MyBatis、Hibernate 等)都依赖反射来实现依赖注入、对象映射等功能。理解反射是阅读框架源码和设计通用组件的基础。

2. 获取 Class 对象

反射的入口是 java.lang.Class 对象,每个被 JVM 加载的类都有唯一对应的 Class 实例。获取该实例的三种方式如下:

1
2
3
4
5
6
7
8
9
// 1. 通过对象实例获取
Student stu = new Student();
Class<?> clazz1 = stu.getClass();

// 2. 通过类字面常量获取
Class<?> clazz2 = Student.class;

// 3. 通过全限定类名动态加载(最常用于框架)
Class<?> clazz3 = Class.forName("com.example.Student");

其中,Class.forName 不仅返回 Class 对象,还会触发该类的静态初始化(执行 static 代码块)。若需仅加载类而不初始化,可使用重载方法 Class.forName(“xxx”, false, classLoader)。

3. 操作构造方法(Constructor)

通过 Class 对象可以获取构造器并创建实例:

1
2
3
4
5
6
7
8
9
Class<?> clazz = Class.forName("com.example.Student");

// 无参构造器
Constructor<?> noArgCon = clazz.getDeclaredConstructor();
Object stu1 = noArgCon.newInstance();

// 带参构造器(String, int)
Constructor<?> hasArgCon = clazz.getDeclaredConstructor(String.class, int.class);
Object stu2 = hasArgCon.newInstance("张三", 20);

getDeclaredConstructor 可获取任意访问权限的构造器,而 getConstructor 仅返回公共构造器。

4. 访问字段(Field)

反射可以获取并修改对象的字段,包括私有字段:

1
2
3
4
5
6
7
8
9
10
// 获取公共字段
Field nameField = clazz.getField("name");
nameField.set(stu1, "李四");

// 获取私有字段并突破访问限制
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 禁用安全检查
ageField.set(stu1, 22);

System.out.println(ageField.get(stu1)); // 22

setAccessible(true) 是反射操作私有成员的关键,但需注意:在高版本 JDK(9 及以上)的模块化环境下,部分内部 API 可能需要添加 JVM 参数 –add-opens 才能正常访问。

5. 调用方法(Method)

通过 Method 对象可以动态调用对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 调用公共无参方法
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(stu1);

// 调用公共带参方法
Method setAge = clazz.getMethod("setAge", int.class);
setAge.invoke(stu1, 25);

// 调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(stu1);

invoke 的第一个参数为目标对象实例(静态方法可传 null),后续参数为方法实参。反射调用方法的性能较直接调用低,因此应避免在热路径中频繁使用。

6. 模拟 Spring IOC 容器

Spring 的控制反转(IOC)容器在创建 Bean 并注入依赖时大量使用了反射。下面是一个简化版的实现示意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 从配置中获取类名
String className = "com.example.StudentService";

// 2. 反射创建 Bean 实例
Class<?> clazz = Class.forName(className);
Object bean = clazz.getDeclaredConstructor().newInstance();

// 3. 查找标有 @Autowired 的字段并注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Object dependency = field.getType().getDeclaredConstructor().newInstance();
field.set(bean, dependency);
}
}

实际框架中,注入逻辑更为复杂(例如使用三级缓存解决循环依赖),但核心机制均基于反射实现。

7. 性能与优化

反射的性能开销主要来自两方面:

元数据查找:方法、字段等元信息存储在 JVM 方法区,每次反射调用都需通过 JNI 查询。

安全检查:每次访问私有成员都会进行权限验证,setAccessible(true) 可以关闭 Java 语言层面的检查,但仍需 JVM 运行时校验。

优化建议:

将 Method、Field 等对象缓存,避免重复调用 getDeclaredMethod。

在高频率调用的场景,考虑使用 MethodHandle(invokedynamic)替代反射。

仅在框架底层或初始化阶段使用反射,业务代码中尽量避免。

实验表明,反射调用的速度比直接调用慢 10~100 倍,因此在性能敏感的循环中应谨慎使用。

8. 反射与泛型

Java 泛型在编译期会进行类型擦除,运行时无法直接获取泛型参数的实际类型。但通过 ParameterizedType 结合反射仍可提取部分泛型信息,常用于框架的类型推断:

1
2
3
4
5
6
7
Type genericSuper = clazz.getGenericSuperclass();
if (genericSuper instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) genericSuper).getActualTypeArguments();
for (Type t : actualTypes) {
System.out.println(t.getTypeName());
}
}

Spring 的 ResolvableType 工具类正是基于此原理构建了强大的泛型解析能力。

9. 总结

框架设计:反射是实现 DI、AOP、ORM 等功能的基石,可大胆使用。

业务代码:尽量避免直接使用反射,以保持代码的可读性和性能。

面试高频:IOC 容器、动态代理、注解解析等底层原理常考。