FEATURED · 精选文章

Java反射机制详解:从原理到实战,掌握反射的核心原理与性能优化

发布时间 / 2026/9/10 1:10:57
来源 / 创域科博编辑部
栏目 / 资讯中心
Java反射机制详解:从原理到实战,掌握反射的核心原理与性能优化 1. 反射到底是什么为什么框架都爱它先聊一个日常场景。你平时写代码创建对象用的是new User()调用方法用的是user.getName()编译器在编译阶段就把类型、方法、字段这些信息定死了。但如果你要写一个通用框架让用户传一个类名进来你就能动态创建对象、调用方法、给字段赋值编译期根本不知道这个类长什么样——这时候就得靠反射Reflection。打个比方你平时用钥匙开自己家的门门是你熟悉的钥匙对孔就进去了。反射相当于你拿到一套万能工具可以把任何一扇门的锁芯拆开看清里面的弹子和弹簧结构然后照着结构去配钥匙。拆开看结构的过程就是获取 Class 对象、拿到构造器、方法、字段的过程。Java 反射的核心机制是 JVM 在类加载完成后会在内存中为每个类生成一个唯一的java.lang.Class对象这个对象里保存了类的完整元数据——类名、修饰符、父类、接口、字段、方法、构造器、注解等等。反射就是通过操作这个 Class 对象来获取和操作类的一切信息。那解决什么问题在没有反射之前你就是 new 一个对象都得在编译期写死类名这导致程序完全丧失动态性。有了反射你可以做到运行时才知道要加载哪个类插件化、热插拔成为可能利用注解和反射写一套通用代码处理不同业务对象ORM、序列化框架都这么干动态代理、AOP 编程直接依赖反射实现如果你是做 Java 面试准备的反射几乎必考。如果你是做框架二次开发的不懂反射看 Spring、MyBatis 源码就像看天书。这篇文章从原理到实战从性能到坑把反射这块硬骨头给你啃透。2. 反射的核心原理与基础操作2.1 Class 对象的三种获取方式获取 Class 对象是反射的第一步网上都说有三种方式但很多人只背结论不知道底层区别在哪里。第一种Class.forName(com.example.User)。这是最经典的全限定名方式你只需要一个字符串JVM 会触发这个类的加载、连接和初始化全过程。注意这里有一个隐藏点——forName默认会执行静态代码块和静态变量初始化如果你只是想拿元数据不想触发静态逻辑可以传入false作为第二个参数。第二种User.class。这种方式是编译期就确定的字面量JVM 加载类的时候就会生成对应的 Class 对象这个过程不会触发静态初始化性能上也比forName要快。这个方式最安全但是要求你在编译期就知道类的确切类型。第三种userInstance.getClass()。这个是运行时通过实例获取属于 Object 类自带的方法。它拿到的永远是运行时实际类型的 Class不是声明类型的 Class。举个例子你写Object obj new User()用obj.getClass()拿到的还是User的 Class。这三种方式在实际项目里怎么选我建议编译期能确定类型用xxx.class只能拿到字符串类名用forName手里只有实例对象用getClass。还有一个小技巧class.isInstance(obj)可以用来替代instanceof在不知道具体类型的情况下判断对象是否为某个类的实例这在泛型擦除后做类型检查时特别有用。2.2 用反射创建对象构造器的正确打开方式拿到 Class 对象之后最常用的操作就是创建实例。很多新手上来就调class.newInstance()这个方法在 JDK 9 开始被标记为废弃官方推荐使用getDeclaredConstructor()配合newInstance()。// 废弃方式的痛点只能调用无参构造器且要求构造器是 public 的 User user (User) Class.forName(com.example.User).newInstance(); // 推荐方式可以指定构造器参数类型也能处理非 public 构造器 Class? clazz Class.forName(com.example.User); Constructor? constructor clazz.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); // 如果是 private 构造器必须打开访问开关 User user (User) constructor.newInstance(张三, 25);这里有几个细节值得注意。第一getConstructor和getDeclaredConstructor的区别前者只能拿到 public 构造器且包含从父类继承来的后者可以拿到当前类所有访问级别的构造器但拿不到父类的。第二setAccessible(true)是暴力破解访问控制的开关它会抑制 Java 的访问权限检查私有构造器、私有方法、私有字段有了这个开关都能碰。第三newInstance里传入的参数类型要和声明的构造器完全一致如果声明的是Integer.class传int.class也不行会直接报 NoSuchMethodException。不过要提醒一句setAccessible(true)也不是万能的。JDK 9 引入模块系统之后如果你的类在某个模块的 package 里没有对当前模块开放opens即使setAccessible(true)也会抛InaccessibleObjectException。很多老项目升级到 JDK 17 后反射代码忽然崩了八成就是这个原因。2.3 方法调用与字段操作从入门到进阶方法调用是整个反射操作里最常用的Spring 的依赖注入、MyBatis 的映射都是靠它实现的。核心 API 就两个getMethod和getDeclaredMethod区别和构造器同理——前者只拿 public 且含继承来的方法后者拿当前类所有方法但不含继承方法。Class? clazz Class.forName(com.example.UserService); // 获取 public 方法包括父类继承的方法 Method publicMethod clazz.getMethod(publicMethod, String.class); // 获取任意方法包括 private 方法 Method privateMethod clazz.getDeclaredMethod(privateMethod, String.class, int.class); privateMethod.setAccessible(true); // 调用方法第一个参数是对象实例第二个开始是方法入参 Object result privateMethod.invoke(serviceInstance, hello, 123);这里最关键的一个坑如果目标是静态方法invoke的第一个参数直接传null就行因为静态方法不依赖实例。JDK 9 之后更严格了会强制检查invoke的第一个参数类型和声明方法的类是否匹配以前传错类型可能还蒙混过关现在直接抛异常。字段操作相对简单getField和getDeclaredField的规则和方法一致读取用field.get(obj)写入用field.set(obj, value)。但是对于final字段情况比较特殊。普通成员变量的final字段在 JDK 12 之前可以通过setAccessible(true)强行修改JDK 12 以后基本改不动了因为 JVM 在内联优化阶段就可能把 final 字段的值编译进调用点。static final字段更是如此比如修改Integer.MAX_VALUE这种操作在主流 JDK 8 以后已经实现了改了但没完全改的效果——直接读是被修改后的值但在某些场景下 JIT 会内联原始常量。所以别指望反射真能改 final 字段这属于 Java 内存模型层面的限制不是反射 API 能突破的。3. 反射的深度能力动态代理与注解处理3.1 动态代理反射的杀手级应用说动态代理离不开反射是因为动态代理在运行时生成了一个全新的代理类而这个代理类里所有方法的调用最终都转发给了InvocationHandler的invoke方法。这个转发过程底层就是反射在起作用。public class LogInvocationHandler implements InvocationHandler { private final Object target; public LogInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(before method: method.getName()); Object result method.invoke(target, args); System.out.println(after method: method.getName()); return result; } } // 生成代理对象的入口 UserService proxy (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new LogInvocationHandler(new UserServiceImpl()) );Proxy.newProxyInstance的三个参数类加载器、接口列表、InvocationHandler。类加载器的作用是把动态生成的代理类加载进 JVM为什么需要指定因为代理类会实现你传入的所有接口它需要和这些接口在同一个类加载器域中才能正常工作。接口列表决定了代理对象长什么样、能调哪些方法。InvocationHandler 就是那个中转站所有对代理对象方法调用都会进到这里。Spring AOP 的逻辑本质上就是这个先判断目标对象有没有实现接口有就用 JDK 动态代理没有就用 CGLIB 生成子类代理。这就是为什么 Spring 里有接口的 Bean 默认用 JDK 代理没有接口的只能用 CGLIB——JDK 动态代理只认接口。特别注意一个调用约定在InvocationHandler.invoke里面不要再去调用代理对象的方法否则会无限递归。因为代理对象上所有方法调用都会回到invoke你再调一次就套娃了。正确做法是调method.invoke(target, args)调用原始目标对象。3.2 注解与反射框架设计的核心组合注解单独用没什么意义它的值不会自动生效必须配合反射去读取和处理。所有 Java 框架里的Autowired、RequestMapping、TableName能在运行时干活靠的都是反射去扫描注解。Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface MyField { String columnName(); String type() default VARCHAR(255); }注意这个Retention(RUNTIME)它是注解能不能被反射读取的前提。如果写成RetentionPolicy.SOURCE只在源码中存在或者RetentionPolicy.CLASS存在字节码中但 JVM 不加载到运行时反射一律读取不到。我见过很多新人写完注解发现反射拿不到检查半天才发现是 Retention 写错了。读取注解的 API 很简单clazz.getAnnotations()能拿到所有运行时注解clazz.getAnnotation(MyField.class)能拿到指定类型的注解。字段和方法同理。你可以用这个机制手写一个简易 ORM定义MyTable和MyField注解然后通过反射读取实体类上的表名和字段映射关系再拼接 SQL 执行。整个过程就是反射读注解、反射读取字段值、然后生成 SQL。这套逻辑理解了MyBatis 的映射原理你就能看透一半。3.3 泛型擦除与反射的碰撞Java 的泛型是编译期的伪泛型运行时类型信息会被擦除。ListString和ListInteger在运行时都是List。但是反射却能在某些场景下打捞回被擦除的泛型信息靠的是 Method 和 Field 对象保留了泛型签名Signature属性。public class GenericDemo { private ListString names; public ListString getNames() { return names; } public static void main(String[] args) throws Exception { Method method GenericDemo.class.getMethod(getNames); Type returnType method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType pt (ParameterizedType) returnType; System.out.println(pt.getActualTypeArguments()[0]); // 输出 class java.lang.String } } }getGenericReturnType和getReturnType的区别就在这里——前者能拿到泛型参数类型后者只能拿到原始类型。如果你用反射解析 JSON 反序列化这个 API 就是你判断目标 List 里装的是哪个具体类型的唯一途径。Jackson、Gson、Fastjson 的 TypeReference 设计本质上就是在绕开泛型擦除把泛型信息通过匿名内部类的方式保留下来。这个方法在写通用 BaseDao、通用 Service 的时候是杀手锏。子类继承BaseDaoUser在父类里反射getGenericSuperclass()就能拿到User的真实类型从而在父类里实现通用的增删改查。如果你理解了这一点再看 MyBatis-Plus 的BaseMapperT是怎么回事就豁然开朗了。4. 反射的性能问题与优化手段4.1 反射到底慢在哪里面试里经常问反射性能差吗答案是差但你要说清楚差在哪。反射慢主要有三个层次的原因不是一句话反射比直接调用慢就完事的。第一层类型检查。反射调用方法时JVM 需要动态地做大量的权限检查和类型匹配比如方法参数类型是否匹配、调用方是否有访问权限。普通方法调用在编译期就完成了类型校验运行时根本不用做这些事。第二层装箱和数组。反射的invoke方法签名是invoke(Object obj, Object... args)意味着你传的每个参数都必须被装箱成 Object基本类型int、long、double会经历装箱和拆箱数组会额外产生一次封装和拆包的损耗。第三层方法查找。getMethod和getDeclaredMethod需要遍历类的方法表做匹配类越复杂、方法越多耗时越长。如果你每个循环都调一次getMethod性能直接爆炸。根据我自己在 JDK 8 和 JDK 17 里做过的基础测试直接调用方法大约在几纳秒级别反射调用基本在几百纳秒到几微秒之间差距大概是 20 到 100 倍。但这个数字在不同 JDK 版本上差异很大JDK 9 以后对反射做了不少优化比老版本好了不少。4.2 缓存、setAccessible 与 MethodHandle既然知道慢的原因就有对应的优化策略。最基础也最有效的是缓存。把获取到的 Method、Constructor、Field 对象缓存起来不要每次调用都重新getMethod查找。查找过程是反射开销里的大头缓存之后只留下 invoke 本身的调用开销性能能提升好几倍。第二个优化点是setAccessible(true)。前面说过它是抑制访问权限检查的开关如果你每次反射调用前都执行一次JVM 需要反复修改访问标志。正确做法是拿到 Method 之后只执行一次setAccessible(true)后续全部复用这个 Method 对象。我在实际项目中试过每个方法调用前都 setAccessible和只设一次相比性能差距能到一倍以上。第三个优化是 JDK 7 引入的java.lang.invoke.MethodHandle它是一种轻量级方法指针有点类似 C 语言的函数指针。它和反射的对比很有意思两者都能实现动态调用但 MethodHandle 的底层是模拟字节码指令的调用JIT 编译器可以对它做内联优化所以性能通常比反射高。代价是 API 比反射难用得多可读性差所以实际项目中用它的不算多。还有一个思路是用 LambdaMetafactory 生成匿名函数。通过反射拿到 Constructor 或 Method 之后用 LambdaMetafactory 将它包装成一个函数式接口的实现后续调用全部用函数式接口来完成这个方法性能接近直接调用。但它的构建成本很高适合构建一次、调用 N 次的场景比如框架启动时注册好所有方法映射运行期只做调用。4.3 一个实际性能对比的例子我不喜欢空谈优化写了个小例子验证一下效果。public class ReflectBenchmark { public static void main(String[] args) throws Exception { Method method ReflectBenchmark.class.getDeclaredMethod(add, int.class, int.class); method.setAccessible(true); ReflectBenchmark instance new ReflectBenchmark(); int loops 1000000; // 直接调用 long start System.nanoTime(); for (int i 0; i loops; i) { instance.add(i, i); } long directNs System.nanoTime() - start; // 不缓存 Method每次重新查找 start System.nanoTime(); for (int i 0; i loops; i) { Method m ReflectBenchmark.class.getDeclaredMethod(add, int.class, int.class); m.invoke(instance, i, i); } long noCacheNs System.nanoTime() - start; // 缓存 Method每轮调用 start System.nanoTime(); for (int i 0; i loops; i) { method.invoke(instance, i, i); } long cacheNs System.nanoTime() - start; System.out.println(direct: directNs / 1000000 ms); System.out.println(no cache reflect: noCacheNs / 1000000 ms); System.out.println(cached reflect: cacheNs / 1000000 ms); } public int add(int a, int b) { return a b; } }这个结果在我本机的 JDK 17 下大概是直接调用 2ms 左右缓存后的反射 30ms 左右不缓存的反射 300ms 以上。差距非常直观。所以性能优化优先级应该是能用编译期手段就别用反射、必须用反射就必须缓存、务必只调一次 setAccessible、超高频率场景考虑 MethodHandle 或 LambdaMetafactory。5. 反射应用场景与框架级案例5.1 框架底层凭什么离不开反射如果你打开 Spring 源码随便一搜就能看到大把ClassUtils、ReflectionUtils的调用。IOC 容器创建 Bean 的方式本质上就是读取配置文件或者注解元数据拿到类名后通过Class.forName加载然后用反射创建实例再通过反射给属性赋值完成依赖注入。Spring 的依赖注入大概做了这三步扫描包路径下的类通过Class.forName加载并把 BeanDefinition 注册到容器创建 Bean 时通过反射调用构造器或工厂方法再对标注了Autowired的字段执行反射赋值。AOP 也一样前面说了 JDK 动态代理依赖反射CGLIB 虽然是字节码生成技术但在回调方法里最终还是要通过反射调用目标方法。MyBatis 更是把反射用到了极致Mapper 接口没有实现类它通过Proxy.newProxyInstance为每个 Mapper 接口生成代理代理里解析方法上的Select、Insert注解拿到 SQL 语句后执行再把结果集通过反射映射成实体对象。5.2 自己动手写一个迷你框架纸上谈兵不如写代码我带你用反射实现一个极简的依赖注入框架体会一下框架设计者的感觉。首先定义一个简单的Component注解标注哪些类需要被容器接管Retention(RetentionPolicy.RUNTIME) Target(ElementType.TYPE) public interface Component { }再来一个Autowired注解标注哪些字段需要自动注入Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface Autowired { }然后写一个容器类启动时扫描指定的包路径找到所有标注了Component的类实例化并注册接着遍历所有 Bean 的字段发现标注了Autowired的字段就反射赋值public class SimpleContainer { private final MapString, Object beans new HashMap(); public void start(String basePackage) throws Exception { // 扫描包下所有类找到 Component 注解的类 SetClass? classes ClassScanner.scan(basePackage); for (Class? clazz : classes) { if (clazz.isAnnotationPresent(Component.class)) { String beanName toLowerCamel(clazz.getSimpleName()); Constructor? constructor clazz.getDeclaredConstructor(); constructor.setAccessible(true); beans.put(beanName, constructor.newInstance()); } } // 依赖注入遍历所有 Bean 的字段 for (Object bean : beans.values()) { for (Field field : bean.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { field.setAccessible(true); Class? fieldType field.getType(); // 根据字段类型找到对应 Bean Object dependency findBeanByType(fieldType); field.set(bean, dependency); } } } } private Object findBeanByType(Class? type) { for (Object bean : beans.values()) { if (type.isAssignableFrom(bean.getClass())) { return bean; } } throw new RuntimeException(No bean found for type: type.getName()); } SuppressWarnings(unchecked) public T T getBean(ClassT clazz) { for (Object bean : beans.values()) { if (clazz.isAssignableFrom(bean.getClass())) { return (T) bean; } } return null; } }这个代码大概 60 行却已经把 Spring 最核心的扫描-注册-注入三个步骤全走了一遍。你会发现框架的本质不复杂复杂的是边界情况、配置灵活性、循环依赖处理、生命周期管理等细节但反射是整个机制运转的前提。5.3 哪些场景反而不建议用反射反射不是万能的有些场景用反射就是给自己挖坑。第一种是高性能热点路径。比如网关里每次请求都要调用的动态逻辑如果你用反射去干活延迟直接拉满。这种场景应该用编译期生成代码或者字节码增强技术比如在编译期用 Annotation Processor 生成代码或者运行期用 ASM 生成字节码避免运行时反射。第二种是暴露了太多内部状态的维护场景。反射的setAccessible(true)可以让你随意改私有字段、调私有方法但代价是绕过了封装代码对类的内部实现产生了强依赖。一旦类的实现变了你的反射代码可能就崩了而且编译器不会给你任何提示只能在运行时报错。第三种是序列化反序列化超高频场景。Fastjson、Jackson 在首版实现里大量使用反射来读取字段、获取 getter/setter 方法后续版本为了性能都引入了缓存反射元数据 ASM 生成序列化器/反序列化器的优化策略。原理还是反射起步但运行时已经不再频繁走反射了。6. 反射的常见问题与避坑清单6.1 高频异常与处理方案反射代码运行时报错很常见关键是能快速定位到原因。我整理一张高频异常对照表方便你出问题时直接查。异常类型触发场景解决方案ClassNotFoundExceptionClass.forName传错了类名或者依赖包没打进来检查类路径、依赖 jar确认是全限定名NoSuchMethodExceptiongetMethod找不到对应方法参数类型不匹配确认方法名和参数类型完全一致谨慎用重载NoSuchFieldException字段名写错或者字段是继承来的检查拼写继承字段请在父类中获取IllegalAccessException没有调用setAccessible(true)或 JDK 模块不允许访问加 setAccessible或者检查模块导出配置InvocationTargetException反射调用的目标方法内部抛了异常被包了一层用 getCause() 获取真正异常不要被外层误导IllegalArgumentException传入参数类型不对或者实例类型不匹配检查 invoke 的第一个参数和整个方法签名InaccessibleObjectExceptionJDK 9 模块系统隔离反射进不了非开放模块的包在启动参数加 --add-opens或调整模块设计InvocationTargetException是反射里最容易踩的坑。你在反射调用方法后 catch 到异常打印堆栈发现根本不是你自己抛的异常类型原来是被包了一层。解决方式是e.getCause()拿到真正的异常再去排查。面试问反射调用方法时捕获到的异常为什么和预期不一样答案就是这个。6.2 边界场景与设计上的坑反射环境下有几个边界场景特别容易被忽略。第一个是内部类的构造器。非静态内部类有一个隐藏的外围类引用参数你写new InnerClass()是没问题的但你用反射getDeclaredConstructor()获取内部类构造器时会发现它有一个隐藏的EnclosingClass类型参数。必须把这个参数也传进去否则会报参数个数不匹配。这个坑我在一个序列化框架里踩过排查了半天。第二个是数组的反射操作。int[].class是合法的但你不能用Class.forName(int[])去加载它要用Class.forName([I)或者直接用Array.newInstance(int.class, 10)创建数组。反射操作多维数组时Array.get和Array.set对一维数组的处理和对多维数组是不同的维度信息在高维情况下特殊处理。第三个是 getMethod 的继承规则。getMethod只能拿到 public 方法但注意它是能拿到父类 public 方法的。如果你在子类反射getMethod(getName)父类的 getName 也能拿到。反过来getDeclaredMethod只能拿到当前类声明的所有方法即使父类有同名方法也拿不到。理解这个规则才不会被那些为什么我反射拿到的方法和你说的不一样的困惑卡住。第四个是泛型和重载的纠缠。同样一个方法名如果存在重载版本你必须把参数类型准确传给getMethod。比如有个setName(String)和setName(Integer)你不传参数类型或者传了Object.class都是找不到方法的。6.3 反射代码的单元测试与自我保护反射代码是典型的编译期畅通无阻、运行期突然爆炸的类型所以单元测试格外重要。我给两个实操建议。一是写测试时用例要覆盖权限场景。同一个反射代码在 public 类、private 内部类、JDK 核心类比如String属于 java.base 模块、第三方依赖类上分别跑一遍你会发现虽然代码一样但表现可能完全不同。JDK 17 下反射访问String的内部字段如果没加--add-opens java.base/java.langALL-UNNAMED直接报错。二是防御性编程底线。反射调用前先判断方法是否存在而不是直接调invoke等它爆。比如做一个工具方法获取方法时失败返回null调用时判空并给出友好提示而不是让异常一层层往上抛。框架代码尤其要注意因为你不知道使用方会传入什么样的类你写的反射代码要扛得住各种奇奇怪怪的输入。7. 高频面试题复盘反射常考的知识点毕竟搜索热词里有一堆 java 面试题这里把反射相关的常见面试问题做一个系统梳理方便你有针对性复习。7.1 反射是什么一句话怎么讲清楚面试官问什么是反射不要背定义用反向和正向对比来说。正常编码是编译期就确定类型、创建对象、调用方法这叫正向。反射是运行时拿到类的元数据据此动态创建对象、调用方法、访问字段就像照镜子一样把类的结构映出来。翻译成人话就是把 Java 类当成一个对象来操作这个对象就是 Class 对象。然后补充适用场景框架的通用化处理、动态代理、注解解析、序列化反序列化、插件化开发都是反射的典型应用。最好再提一句 Spring 的 IOC 和 AOP毕竟框架相关的问题都能扯到这个点上。7.2 获取 Class 对象的三种方式有什么区别这是最基础但几乎必考的问题。回答时候从三个维度区分来源类名、实例、字面量、触发初始化时机forName 默认触发静态初始化其余两种不触发、性能字面量最快。重点说下Class.forName触发静态初始化这个细节因为例子就是经典的Class.forName(com.mysql.jdbc.Driver)JDBC 4.0 之前必须写这句才能注册 Driver就是因为要触发静态代码块里的DriverManager.registerDriver。7.3 反射为什么慢有什么优化手段按前面说的三层原因回答类型安全检查和权限检查的开销、基本类型的装箱拆箱与数组开销、方法查找与遍历开销。优化手段按优先级缓存反射对象、只调用一次 setAccessible、用 MethodHandle 替代反射、用 LambdaMetafactory 生成高阶函数。最后补一句如果反射超过总耗时的 5%就该考虑架构层面优化而不是抠反射细节这句话能体现你的工程判断力。7.4 setAccessible(true) 的原理和风险原理是 Java 访问控制的开关调用后 JVM 跳过对该成员的权限检查。风险是破坏了封装把私有方法公开出去而且 JDK 9 模块系统对setAccessible加了限制越过模块边界访问会抛InaccessibleObjectException。这题一般会追问你怎么实现一个私有方法的单元测试答案就是反射加 setAccessible。7.5 动态代理和反射有什么关系JDK 动态代理基于反射核心在于Proxy.newProxyInstance生成代理类代理类上所有方法调用都会委托给InvocationHandler.invoke而invoke方法里的Method参数就是通过反射获取的目标方法。Spring AOP 在目标对象有接口时用 JDK 动态代理没接口时用 CGLIB。这题回答得好顺带展示你对 Spring 底层的理解。7.6 反射在哪里读到了泛型信息泛型在运行时被擦除但 Method 和 Field 上保留了 Signature 属性通过getGenericReturnType、getGenericSuperclass、getActualTypeArguments可以拿到泛型参数。典型应用就是 JSON 反序列化里的 TypeReference以及通用 DAO 里通过父类泛型获取实体类型。8. 我的实操心得与建议最后分享一点我个人的经验不是教科书上能看到的。反射这东西框架工程师每天在用业务开发经常用不到。但如果你想进阶读框架源码、写一些通用组件反射是迈不过去的坎。我最深的体会是反射代码一定要写在专门的工具类里不要散落在业务方法中。我见过不少同事直接在业务逻辑里写一段Class.forName后来类名一改运行期才炸排查成本比写代码成本高得多。另外JDK 版本迭代对反射的影响比你想的大。JDK 8 换到 JDK 17如果遇到InaccessibleObjectException不要急着骂框架先看是不是模块系统导致的访问限制Java 启动参数加--add-opens通常能解决。高性能场景我建议先压测再优化别把反射的性能问题当成玄学。我负责过的一个定时任务用了缓存后的反射和直接调用性能差距完全可接受百万次调用在几十毫秒级别不构成瓶颈。最后一个小技巧如果你在反射获取构造器或方法时经常因为参数类型头疼可以写一个工具方法帮你自动匹配最合适的重载版本遍历一下getDeclaredMethods或者getDeclaredConstructors用参数列表做匹配。这个工具类在很多通用组件里能复用值得花点时间。反射没有想象中难也没有想象中简单。它的核心思想就一句话把类本身变成可操作的对象。理解了这句话其他都是 API 层面的扩展。多写几个小例子把 Spring 的依赖注入和动态代理自己模拟一遍这块内容你就真正吃透了。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻