高 进阶
Java反射机制#
一句话答案#
反射在运行时获取类信息并操作其成员(字段/方法/构造器),是 Spring IoC/AOP、ORM 框架的基础,但有性能开销。
核心要点
反射三要素: Class(类的元信息)、Method(方法)、Field(字段)、Constructor(构造器)。
- 一切起点是 Class 对象:它是类加载(加载阶段)的产物,由 JVM 在方法区/元空间创建,每个类在每个类加载器下只有一份。
Method/Field/Constructor都从 Class 对象里取出。
获取 Class 的三种方式:
| 方式 | 写法 | 特点 |
|---|---|---|
| 类字面量 | String.class | 编译期确定,不触发类初始化,最快 |
| 实例方法 | obj.getClass() | 运行时拿对象的实际类型(多态) |
| 全限定名 | Class.forName("com.x.Foo") | 运行时按名字加载,会触发类初始化(执行静态块),框架/JDBC 加载驱动常用 |
为什么反射慢、怎么优化(深挖点):
Method.invoke背后是 MethodAccessor。JVM 对每个 Method 有两套实现,按调用次数动态切换(inflation 膨胀机制):- 前若干次(
sun.reflect.inflationThreshold默认 15)走NativeMethodAccessorImpl,本质是 native 调用,每次都要跨 JNI、做参数包装/拆装箱,慢但启动无额外开销。 - 超过阈值后,JVM 动态生成一个字节码类
GeneratedMethodAccessorN直接调用目标方法,避免 JNI 开销,后续调用快很多(代价是首次生成字节码 + 类加载)。
- 前若干次(
setAccessible(true)跳过访问权限检查(不再每次校验 public/private、调用方包名),省下一大块开销,也能访问私有成员。- 缓存
Method/Field/Constructor对象,避免重复getDeclaredMethod的查找与数组拷贝。 - MethodHandle 比反射更快:它在创建时就把方法直接绑定好(一次性解析),调用路径短、且能被 JIT 内联优化,接近直接调用;反射的 invoke 难以内联。
LambdaMetafactory(lambda 底层)也基于它。
典型应用: 框架的运行时动态能力都靠反射——Spring IoC 反射创建 Bean 并注入、AOP 反射 + 动态代理增强、MyBatis/Hibernate 等 ORM 反射把结果集映射成对象、JSON 库反射读写字段。
面试回答(2分钟版)
反射是 Java 提供的在运行时动态获取类信息并操作其成员的能力。通过 Class 对象可以获取一个类的构造器、方法和字段,进而在运行时动态创建实例、调用方法、读写属性,即使是 private 成员也可以通过 setAccessible(true) 绕过访问控制。反射的核心价值在于解耦和灵活性,是 Spring 框架的基石:Spring IoC 容器通过反射根据配置动态创建 Bean 并注入依赖,Spring AOP 通过反射加动态代理实现切面增强,ORM 框架通过反射将数据库记录映射为 Java 对象。反射的性能开销主要来自三方面:每次调用需要做安全检查、方法查找无法利用编译期优化、以及 JIT 编译器无法对反射调用做内联优化。优化方式包括 setAccessible(true) 跳过安全检查、缓存 Method 和 Constructor 对象避免重复查找、使用 MethodHandle 作为更高性能的替代方案。虽然反射有性能开销,但框架启动阶段和非热路径使用反射影响并不大,Spring 整个生态都基于反射运作。
追问与易错
追问方向:
- “反射的性能开销在哪?”→ 主要来自三方面:每次调用的安全权限检查、运行时方法/字段查找开销、JIT 编译器无法对反射调用做内联优化
- “Spring 为什么大量使用反射?”→ IoC 容器需要根据配置/注解在运行时动态创建 Bean 实例并注入依赖,AOP 需要动态代理增强方法,这些都依赖反射的运行时动态能力
- “如何优化反射性能?”→ setAccessible(true) 跳过安全检查、缓存 Method/Constructor 对象避免重复查找、使用 MethodHandle 作为更高性能的替代、框架启动阶段集中反射后续走缓存
易错点:
- ❌ “反射很慢不能用”——Spring 整个框架基于反射,热路径优化后影响不大
- ❌ 忘记 setAccessible(true) 绕过安全检查提升性能