一句话总结
反射在运行时动态操作类(获取 Class → 创建实例 → 访问字段/方法,setAccessible 绕过权限)。核心应用:Spring IoC 注入、MyBatis ORM 映射、JUnit 测试、JSON 序列化。性能低于直接调用(权限检查+装箱+无法内联),枚举单例可防反射破坏。
初级理解
反射(Reflection)是 Java 在运行时动态获取类的信息并操作类的能力。通过反射,可以在运行时获取类的构造方法、字段、方法,并调用它们,即使这些成员是私有的。
核心 API:
// 获取 Class 对象的三种方式
Class<?> clazz1 = Class.forName("com.example.User"); // 通过全限定名
Class<?> clazz2 = User.class; // 通过类字面量
Class<?> clazz3 = user.getClass(); // 通过实例
// 创建实例
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Object obj = constructor.newInstance("张三");
// 访问私有字段
Field field = clazz.getDeclaredField("name");
field.setAccessible(true); // 绕过访问控制
String name = (String) field.get(obj);
// 调用私有方法
Method method = clazz.getDeclaredMethod("secretMethod");
method.setAccessible(true);
method.invoke(obj);
中级深入
反射的性能影响:反射比直接调用慢很多,主要原因:
1. 需要检查访问权限:每次调用都要检查 setAccessible
2. 需要装箱拆箱:invoke 的参数和返回值都是 Object,基本类型需要装箱
3. 方法内联失效:JIT 无法对反射调用进行内联优化
优化建议:使用 setAccessible(true) 关闭访问检查;缓存 Method/Field 对象避免重复查找;JDK 7+ 的 MethodHandle 性能优于反射。
setAccessible 的安全限制:JDK 9+ 模块化系统对反射做了限制,默认不允许反射访问其他模块的非公开成员。可以通过 --add-opens JVM 参数开放。
高级拓展
反射在框架中的典型应用:
1. Spring IoC:通过反射创建 Bean 实例、注入依赖
2. MyBatis:通过反射将 ResultSet 映射到 Java 对象
3. JUnit:通过反射调用 @Test 注解标记的方法
4. JSON 序列化:Jackson/Gson 通过反射读写对象字段
反射破坏单例模式:即使构造方法是 private 的,反射仍可通过 setAccessible 调用,从而创建多个实例。枚举单例可以防止反射攻击,因为 JVM 禁止反射创建枚举实例。
// 反射破坏单例
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton instance2 = c.newInstance(); // 创建了第二个实例!
// 枚举单例:反射无法破坏
enum Singleton {
INSTANCE;
}
// Constructor.newInstance() 对枚举会抛出异常
面试加分项:能说出反射在 Spring/MyBatis 中的具体应用,以及反射破坏单例的防御方案。
实战场景
场景:简单 IoC 容器实现
// 用反射实现简易依赖注入
public class SimpleContainer {
private Map<Class<?>, Object> beans = new HashMap<>();
public void register(Class<?> clazz) throws Exception {
Constructor<?> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
Object bean = ctor.newInstance();
// 注入 @Autowired 字段
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(bean, beans.get(field.getType()));
}
}
beans.put(clazz, bean);
}
}
场景:反射性能优化
// 缓存 Method 对象,避免重复查找
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
public static Object invokeCached(Object target, String methodName, Object... args) {
Method method = METHOD_CACHE.computeIfAbsent(methodName, name -> {
try {
return target.getClass().getDeclaredMethod(name);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
method.setAccessible(true); // 关闭访问检查
return method.invoke(target, args);
}
面试模拟
Q:反射有哪些应用场景?
A:1) Spring IoC:反射创建 Bean、注入依赖;2) MyBatis:反射将 ResultSet 映射到 Java 对象;3) JUnit:反射调用 @Test 方法;4) Jackson/Gson:反射读写对象字段做 JSON 序列化;5) 动态代理:JDK 代理依赖反射调用目标方法。
Q:反射为什么慢?如何优化?
A:慢的原因:1) 每次调用检查访问权限;2) 参数/返回值需要装箱拆箱;3) JIT 无法内联反射调用。优化:setAccessible(true) 关闭权限检查;缓存 Method/Field 对象避免重复查找;JDK 7+ 可用 MethodHandle 替代,性能接近直接调用。