反射的原理和应用场景?

2025年 阅读约 8 分钟 面试指南 · Java面试

深入解析Java反射机制的原理和应用场景,从基本API到框架底层,分初级、中级、高级三个层次全面讲解。

一句话总结

反射在运行时动态操作类(获取 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 替代,性能接近直接调用。