反射主要用于框架底层而非业务代码,如Spring Bean初始化、MyBatis字段映射、JUnit测试执行和Jackson序列化;业务中滥用会引发性能、封装与模块访问问题。

反射常用于框架底层,不是业务代码的常规选择
Java反射机制的核心价值不在日常开发,而在Spring、MyBatis、JUnit这类框架内部。它让框架能在运行时动态加载类、调用方法、访问私有字段——而这些操作在编译期完全不可知。如果你在业务逻辑里频繁写 Class.forName() 或 Method.invoke(),大概率设计出了问题。反射带来性能开销、破坏封装、绕过编译检查,还可能被模块系统(Java 9+)限制访问。
哪些场景下框架必须用反射
框架需要“对未知类型做统一处理”,这是反射不可替代的典型场景:
-
Spring Bean 初始化:读取
@Component注解后,用Class.getDeclaredConstructor().newInstance()实例化类,而非硬编码 new -
MyBatis 结果集映射:执行
ResultSet查询后,通过clazz.getDeclaredField("id")+field.setAccessible(true)把数据库列值塞进目标对象私有字段 -
JUnit 5 测试发现与执行:扫描所有含
@Test的 public 方法,用method.invoke(testInstance)运行,不依赖测试类继承特定父类 -
JSON 序列化(如 Jackson):对任意 POJO 调用
getDeclaredFields()获取全部字段,再结合field.get(obj)读取值
面试常问的反射性能与替代方案
面试官问“反射慢在哪”,不是让你背 JVM 源码,而是看是否理解关键瓶颈:
-
Class.forName()和getDeclaredMethod()触发类加载和元数据解析,开销大——应缓存Method对象,避免重复查找 -
Method.invoke()默认走委派逻辑,JDK 7 后对 public 方法做了优化,但仍有安全检查开销;可调用setAccessible(true)跳过访问控制(注意模块限制) - 真正高频调用场景(如 RPC 参数序列化),主流方案已转向
MethodHandle(更轻量)或代码生成(如 ByteBuddy、Javassist 编译期织入)
示例:缓存 Method 避免重复查找
立即学习“Java免费学习笔记(深入)”;
private static final MapMETHOD_CACHE = new ConcurrentHashMap<>(); public static Object invokeGetter(Object obj, String fieldName) throws Exception { String key = obj.getClass().getName() + "." + fieldName; Method method = METHOD_CACHE.computeIfAbsent(key, k -> { try { return obj.getClass().getMethod("get" + capitalize(fieldName)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } }); return method.invoke(obj); }
模块系统(Java 9+)下反射容易被静默失败
如果项目用了 module-info.java,默认禁止反射访问其他模块的非导出包,setAccessible(true) 会直接抛 InaccessibleObjectException,而不是早期的 IllegalAccessException。
- 解决方式不是关模块系统,而是显式在
module-info.java中开放:opens com.example.model to java.base; - 启动参数也可临时放宽:
--add-opens java.base/java.lang=ALL-UNNAMED(仅调试用) - Maven 插件(如 maven-compiler-plugin)需设
release为 8 或更低,否则编译器会拒绝生成反射相关字节码
这个限制在 Spring Boot 2.6+ 和 JDK 17 合体时踩坑最多——看似一样的反射代码,换环境就崩,根源常在这里。










