Appearance
🔍 反射机制
反射概述
反射(Reflection)允许程序在运行时获取类的信息并操作对象。
源码 编译 运行期
┌──────────┐ ┌──────────┐ ┌───────────────┐
│ .java │→ │ .class │ → │ Class<?> 元信息│
└──────────┘ └──────────┘ └──────┬────────┘
│
反射 API(Field/Method/Constructor)
│
invoke/get/set 等操作反射的典型应用:框架的依赖注入(DI)、序列化、对象映射、AOP、SPI 服务加载等。
Class 对象
获取 Class 对象
java
// 方式1:通过类名
Class<?> clazz = String.class;
// 方式2:通过对象
String str = "Hello";
Class<?> clazz = str.getClass();
// 方式3:通过类名字符串
Class<?> clazz = Class.forName("java.lang.String");类加载与可见性(与模块化的关系)
ClassLoader 层次:
Bootstrap → Platform → Application → 自定义
双亲委派:优先父加载,避免核心类被覆盖。
JPMS:未导出的包默认对外不可见,反射需配合 opens。bash
# 运行期开放反射(示例)
java --add-opens com.example.app/com.example.pkg=org.hibernate.orm.core创建对象
使用反射创建对象
java
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.Person");
// 创建实例
Person person = (Person) clazz.newInstance();
// 使用构造方法
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("张三", 25);注意:Class#newInstance() 已过时,推荐使用构造器对象;配合缓存避免重复开销。
java
private static final ConcurrentMap<List<Class<?>>, Constructor<?>> CTOR_CACHE = new ConcurrentHashMap<>();
static <T> T newInstance(Class<T> type, Class<?>[] sig, Object... args) {
try {
Constructor<?> c = CTOR_CACHE.computeIfAbsent(Arrays.asList(sig), k -> {
try { Constructor<?> cc = type.getDeclaredConstructor(k.toArray(new Class<?>[0])); cc.setAccessible(true); return cc; }
catch (Exception e) { throw new RuntimeException(e); }
});
@SuppressWarnings("unchecked") T t = (T) c.newInstance(args);
return t;
} catch (Exception e) {
throw new RuntimeException(e);
}
}访问字段
java
Class<?> clazz = Person.class;
// 获取字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 允许访问私有字段
// 获取和设置值
Person person = new Person();
nameField.set(person, "李四");
String name = (String) nameField.get(person);小贴士:频繁调用 setAccessible(true) 有安全与性能代价;优先批量初始化后复用 Field 引用。
调用方法
java
Class<?> clazz = Person.class;
// 获取方法
Method method = clazz.getMethod("setName", String.class);
// 调用方法
Person person = new Person();
method.invoke(person, "王五");MethodHandle 与 VarHandle(高性能反射)
MethodHandle 提供轻量、可内联的调用句柄;VarHandle 提供受控的内存语义访问(含原子/有序)。
java
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(Person.class, "setName",
MethodType.methodType(void.class, String.class));
mh.invokeExact(new Person(), "赵六");java
VarHandle NAME;
static {
try {
NAME = MethodHandles.lookup()
.findVarHandle(Person.class, "name", String.class);
} catch (Exception e) { throw new RuntimeException(e); }
}
// 原子/有序操作示例
NAME.setRelease(person, "钱七");性能要点:JIT 可对 MethodHandle 优化内联;常规反射 Method#invoke 通常更慢。
完整示例
java
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.Person");
// 创建对象
Person person = (Person) clazz.newInstance();
// 访问字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "张三");
// 调用方法
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(person, "李四");
System.out.println(person.getName());
}
}性能、可维护与安全边界
- 性能:
- 反射调用慢于直呼;
MethodHandle通常优于Method#invoke;缓存元数据对象。 - 避免热路径上频繁反射;可用代码生成(ByteBuddy/Javassist)或记录-回放模式。
- 反射调用慢于直呼;
- 可维护:
- 反射绕过封装,API 变更难以被编译期发现,需完善测试。
- 安全与模块化:
- JPMS 下未导出包不可反射访问,需
opens精确授权到特定模块。 - 最小权限原则:避免
open module全量开放。
- JPMS 下未导出包不可反射访问,需
图示:反射与模块化访问矩阵
| 源码可见 | 编译可见 | 运行期可反射 | 备注
---------------+---------+---------+------------+----------------------
exports | ✔ | ✔ | ✖ | 需另行 opens 才可反射
opens | ✖ | ✖ | ✔ | 仅限运行期反射
exports+opens | ✔ | ✔ | ✔ | API + 反射都可用
未导出/未开放 | ✖ | ✖ | ✖ | 默认强封装实战:Jackson 在 JPMS 下的最小配置
java
module com.example.app {
requires com.fasterxml.jackson.databind;
opens com.example.app.dto to com.fasterxml.jackson.databind;
}实战:JPA/Hibernate 实体扫描
java
module com.example.app {
requires org.hibernate.orm.core;
opens com.example.app.entity to org.hibernate.orm.core;
}下一步
掌握了反射机制后,可以继续学习:
💡 提示:反射提供了强大的动态编程能力,但要谨慎使用
