尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入理解Java反射:原理、应用与性能避坑指南

深入理解Java反射:原理、应用与性能避坑指南 我在带项目组时经常被问到同一个问题反射到底是什么为什么Spring、MyBatis这些框架动不动就提到反射也有不少工作了两三年的Java开发背面试题时“反射就是运行时获取类的信息”这句话说得比谁都顺可真让他用反射去实现一个通用导出工具或者排查一个NoSuchMethodException马上就卡壳。这篇文章我就想用实际场景把Java反射这件事彻底讲透从JVM层面的加载机制到API的使用细节再到Spring、动态代理这些框架里到底怎么用反射最后把我踩过的坑和性能优化的经验一并交代清楚。不管你是刚学完Java基础准备进阶的初学者还是想系统梳理反射知识点的面试候选人这篇内容都值得你认真看一遍。1. 反射到底是什么一次运行时“照镜子”的机制1.1 一句话讲清反射的核心先给一个最直白的定义反射是Java提供的、在运行时获取和操作类信息的能力。正常写代码时我们是“先有类再new对象”编译期就知道这个类有什么方法、什么字段。反射把这条路倒过来——程序运行起来之后通过一个Class对象去反推这个类的完整结构拿到构造函数、方法、字段然后动态地创建对象、调用方法、修改字段值。我用一个生活化的类比来解释。你去一家餐厅吃饭正常流程是看菜单点菜后厨按菜单做菜这是“正向调用”。反射相当于你直接走进后厨把冰箱打开看看里面有什么食材翻翻厨师的操作手册然后自己动手做一道菜。前者是编译期约定好的流程后者是运行时“侦察”出来的能力。这个能力在框架开发中极其重要因为框架在编译期根本不知道你会写一个什么样的类它只能等到运行时拿到你的类再通过反射去调用你写的方法。1.2 Java类在JVM中的“三阶段”加载过程要理解反射必须先搞清楚一个类从源代码到能被反射使用的完整过程。Java类在JVM里经历三个阶段加载、连接、初始化合起来叫类加载。加载阶段JVM通过类加载器把.class文件读进内存生成一个Class对象。这个Class对象就是反射的入口它代表整个类的元信息。注意一个关键点一个类在JVM中只会被加载一次对应唯一的Class对象不管这个类被new了多少次Class对象都是同一个。这一点可以用来做类加载校验比如判断两个对象是不是同一个类创建的不需要比较全限定名直接比较getClass()返回的Class对象是否相等即可。连接阶段又细分为验证、准备、解析三步。验证是检查字节码安全准备是为静态变量分配内存并设置默认值解析是把符号引用替换为直接引用。初始化阶段才真正执行静态代码块和静态变量的赋值。反射有可能会触发类的初始化但如果你用的是Class.forName的initialize参数或者某些只拿方法名的操作可能不会触发完整初始化这个细节经常被忽略。举个实际场景。我们在做日志系统时需要打印每个对象的所有字段值但日志模块不可能预先知道你会传入什么样的对象。这时候Class.getDeclaredFields()配合Field.get()就能在运行时拿到任何对象的字段名和值这就是反射在通用工具里最典型的应用。2. 反射API全家桶从Class对象到Method实战2.1 获取Class对象的三种姿势反射的入口是Class对象Java提供了三种获取方式使用场景完全不同。第一种.class语法。比如String.class这种方式在编译期就确定了最安全也最快但前提是你写代码时已经知道类的名字。第二种obj.getClass()通过实例获取适合在方法内部拿到具体对象的类型信息。第三种Class.forName(com.example.Demo)只需要传入类的全限定名字符串这是框架中最常用的方式因为框架拿到的是一个配置字符串必须通过路径去加载类。三种方式的区别我整理成了一张表获取方式写法编译期依赖典型场景类字面常量String.class有代码中明确知道类型实例方法obj.getClass()无方法内获取实际对象类型全限定名Class.forName(java.lang.String)无配置文件、动态加载这里有个很容易踩的坑Class.forName在加载类时会执行静态代码块而ClassLoader.loadClass不会。如果你用Class.forName加载一个含有复杂静态逻辑的类可能会触发意想不到的副作用。我用这个特性做过一个技巧性的操作用Class.forName加载JDBC驱动时驱动类的静态块里会主动向DriverManager注册自己这样后面DriverManager.getConnection才能工作。如果误用ClassLoader.loadClass连接就会失败而且报错非常隐晦。2.2 构造函数、字段、方法的获取与调用Class对象拿到手之后核心操作就是三件事拿构造函数创建对象、拿字段读写值、拿方法执行调用。构造函数获取有两个方法getConstructors()返回public构造函数getDeclaredConstructors()返回所有构造函数。两者的区别是Declared版本能拿到private成员。创建对象时JDK 9之前常用newInstance()但它只能调用无参构造函数如果类没有无参构造直接抛InstantiationException。我强烈建议优先使用constructor.newInstance(args)这种方式因为可以明确指定参数也便于处理异常。字段获取同理getField对应public字段getDeclaredField对应所有字段包括private。注意一点getField只能拿到public字段而且包括从父类继承来的public字段getDeclaredField只能拿当前类自己声明的字段父类字段拿不到。这个特性在实际开发中特别容易出问题。我在写一个对象属性差异对比工具时要比较两个对象的所有字段包括父类字段一开始用getDeclaredFields只拿到了子类的字段父类的字段全部丢失排了半天才发现问题。正确的做法是从当前类开始沿着父类链逐层收集。调用方法也分两个维度getMethod拿public方法包括继承的getDeclaredMethod拿当前类的所有方法。调用时用method.invoke(target, args)如果方法是private的必须先调用setAccessible(true)。这个setAccessible是很多初学者不理解的地方它本质上是告诉JVM我要访问一个私有成员请关闭访问权限检查。这个操作会绕过Java的访问控制所以反射也被称为“打破封装”的能力。2.3 反射调用的核心流程与示例我写一个完整的示例演示从加载类到调用方法的全过程这是框架开发的基础模板。public class ReflectDemo { public static void main(String[] args) throws Exception { // 1. 通过全限定名加载类 Class? clazz Class.forName(com.example.UserService); // 2. 获取有参构造函数并创建实例 Constructor? constructor clazz.getDeclaredConstructor(String.class); constructor.setAccessible(true); Object instance constructor.newInstance(张三); // 3. 获取私有方法并调用 Method method clazz.getDeclaredMethod(privateMethod, String.class); method.setAccessible(true); Object result method.invoke(instance, 反射参数); System.out.println(result); } }这个流程看起来简单但每一步都有讲究。第二步中getDeclaredConstructor拿到构造函数后setAccessible(true)是必须的因为UserService的构造函数可能是private的。第三步中method.invoke的第一个参数是目标对象第二个起是方法入参如果方法是静态的第一个参数传null即可。另外invoke方法抛出的异常都包装在InvocationTargetException里面需要用getCause()才能拿到原始异常这个细节排查问题时特别有用。3. 反射能做什么高价值应用场景深度拆解3.1 框架的基石Spring的依赖注入与Bean管理很多人背Spring的IoC原理时都会背到“反射”但始终没搞明白反射在里面到底做了什么。我拆开讲。Spring容器启动时会扫描你配置的包路径找到所有标注了Component、Service这些注解的类。此时Spring并不知道这些类的具体类型它只能拿到类的全限定名字符串然后通过Class.forName加载这些类再用反射获取构造函数创建Bean实例。创建之后Spring还要处理依赖注入比如某个字段标了AutowiredSpring通过反射找到这个字段再从容器中找到对应的Bean用Field.set()方法把依赖塞进去。这个过程如果不用反射几乎不可能实现。因为你写的UserService依赖UserDao这是你代码里的业务逻辑Spring在编译期看不到这些关系。只有到运行时Spring通过反射查看UserService的字段声明发现有一个UserDao类型的字段才能做注入。这就是为什么IoC容器被称为“控制反转”——创建对象和装配依赖的控制权从你的代码转移到了框架手里而反射就是框架手里的“万能手”。3.2 动态代理Spring AOP的底层实现逻辑动态代理是反射的另一个重量级应用Spring AOP、MyBatis的Mapper代理都是基于它实现的。JDK动态代理的核心是Proxy.newProxyInstance方法它需要三个参数类加载器、接口数组、InvocationHandler处理器。当外部调用代理对象的方法时实际会进入InvocationHandler.invoke方法在这里你可以做前置增强、后置增强然后通过反射调用目标对象的真实方法。public class LogProxyHandler implements InvocationHandler { private Object target; public LogProxyHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(方法执行前参数 Arrays.toString(args)); Object result method.invoke(target, args); System.out.println(方法执行后结果 result); return result; } }看到没有method.invoke就是反射。AOP的“切面”之所以能在你的业务方法前后插入日志、事务、权限控制靠的就是这一行代码。MyBatis的Mapper接口也是同理框架根据接口的Method对象拼接SQL、执行查询、映射结果整个过程都是动态的。3.3 通用工具与案例对象拷贝、注解解析、JSON序列化反射在业务代码里也有大量用武之地我用三个最常见的场景说明。对象属性拷贝工具是反射入门级的练手项目。当我们做分层的DTO、VO转换时如果一个个字段去set字段一多就非常痛苦。用反射可以写一个通用的拷贝方法拿到源对象的所有字段遍历赋值给目标对象的同名同类型字段。我用过一个叫BeanUtils.copyProperties的工具类底层就是干这个的核心代码不过几十行。注解解析是反射的另一大用途。框架代码里经常看到自定义注解比如权限注解RequiresPermission(admin)。程序运行时通过反射获取方法上的注解判断当前用户是否有权限执行该方法有权限就放行没权限就拦截。Spring的Transactional、GetMapping都是这样解析的注解本身只是标记真正干活的还是反射。JSON序列化也是反射的重度用户。你调用JSON.toJSONString(obj)时序列化框架并不知道你的对象有哪些字段它只能通过反射获取对象的字段列表再逐个读取值拼接成JSON字符串。反序列化同理先通过反射创建对象再通过反射给字段赋值。有些框架为了性能会缓存反射拿到的字段信息但第一次解析对象结构时必然要走反射。4. 反射的性能代价与实战避坑指南4.1 为什么反射比直接调用慢网上说“反射性能差”但不讲为什么差这就让人很困惑。反射慢主要有三个原因类型检查、自动装箱、访问权限检查。类型检查指的是反射调用时方法的参数是Object[]JVM必须检查每个参数的类型是否匹配这个检查比编译期直接调用要消耗更多时间。自动装箱是因为你传基本类型如int时会被装箱成Integer这本身就有开销。访问权限检查是setAccessible之后可以跳过的步骤所以一个常见的优化就是提前调用setAccessible(true)省掉权限校验。还有一个容易被忽略的点反射调用方法时参数数组是可变长度的JIT编译器很难像直接调用那样做内联优化。直接调用一个方法经过JIT优化后性能极高反射调用因为要动态解析JIT的优化空间被大幅压缩。我实测过一个基准测试未优化的反射调用比直接调用慢大概3到5倍但提前setAccessible(true)并缓存Method对象后性能差距能缩小到2倍以内。大多数业务场景下这个开销可以接受但在高频循环里还是应该避免使用。4.2 高频异常与排查思路速查表反射代码的异常比普通代码多而且很多异常信息非常隐晦我把最常见的几种问题和排查思路整理成表格异常类型出现原因排查思路ClassNotFoundExceptionClass.forName传的类路径不存在检查全限定名拼写、check jar包是否引入NoSuchMethodException方法名或参数不匹配确认方法签名包括参数类型和顺序IllegalAccessException访问private成员前未调用setAccessible(true)检查权限设置注意模块系统限制InvocationTargetException目标方法内部抛出的异常被包装调用getCause()查看原始异常InstantiationException反射创建对象失败通常是抽象类或接口确认类有可用的构造函数ClassCastException反射获取的返回值类型强转错误打印方法返回类型后再做处理InvocationTargetException是我见过开发人员最容易被绕晕的异常。你调用的方法内部抛了一个NullPointerException但反射层会把它包成InvocationTargetException抛出来如果你不拆开看cause永远找不到真正的错误。我的习惯是写反射工具方法时统一处理这个异常直接抛出cause让上层看到原始错误。4.3 实战心得一套更安全的反射写法使用反射时我的经验可以浓缩成几条建议。第一缓存反射对象。Class、Method、Field对象在获取之后应该缓存复用不要每次都重新获取。每次getMethod都要遍历类的方法列表做比对这个开销比调用方法本身还大。我写框架时一般用ConcurrentHashMap做缓存以方法签名或者类名做key。第二一定要setAccessible(true)。无论是private还是public成员统一设置一次减少运行时的权限检查。这在一开始可能没有明显感觉但在大批量调用时性能差异会累积得很明显。第三泛型和反射结合时要小心类型擦除。方法参数里写了ListString但反射拿到的方法签名里参数类型是List泛型信息是被擦除的。如果你想获取泛型参数的具体类型必须使用ParameterizedType去解析这在编写通用仓储时是个进阶技巧。第四setAccessible在JDK 17之后有模块系统限制。Java 9引入模块化后如果你的类在一个加了强封装的模块里即使调用setAccessible(true)也可能抛InaccessibleObjectException。解决办法是启动参数加--add-opens java.base/java.langALL-UNNAMED这个我在升级JDK版本时踩过坑分享出来给大家做个提醒。我还要多提一句反射虽然强大但滥用会让代码可读性变差。我见过有人把简单的getter调用写成反射理由是想做“通用”。但业务代码里没有必要为了通用而通用反射应优先用于框架层、基础组件层业务层能用多态解决的就别上反射。过度抽象和过度设计在工程上都是减分项。5. 从反射到动态代理JDK代理与CGLIB的选择策略5.1 JDK动态代理的关键限制上一节我讲了AOP离不开反射这里再展开讲一个面试高频考点JDK动态代理和CGLIB的区别以及Spring为什么有时候用JDK代理有时候用CGLIB。JDK动态代理只能代理接口Proxy.newProxyInstance的第二个参数要求传入接口数组。如果你的目标类没有实现任何接口JDK代理直接就废了。原理是这样的JDK在运行时动态生成了一个实现指定接口的代理类这个代理类继承了Proxy类。Java是单继承的代理类已经继承了Proxy就无法再继承你的业务类所以只能靠实现接口来代理。这个限制在日常开发中影响不小。很多老项目的Service类并没有提取接口直接写了一个具体的类。这时如果用JDK代理Spring启动时会直接报错。我对这个坑印象特别深有一次同事加了AOP功能结果启动时报Cannot proxy target class because CGLIB2 is not available就是目标类没有接口又没引入CGLIB导致的。5.2 CGLIB代理与反射的关系CGLIB绕过接口的限制用的是生成子类的方式。它在运行时动态生成目标类的子类子类覆盖目标类的方法来实现代理增强。因为用的是继承所以目标类不能是final的被代理的方法也不能是final的否则无法重写。CGLIB底层用的是ASM字节码技术直接操作字节码生成新的子类这一点和JDK代理不同——JDK代理还是会调用反射来处理方法的转发CGLIB生成子类后方法调用走的是方法重写的机制性能上通常比JDK代理更好。但不要误会CGLIB完全不用反射。在Spring的管理体系中Bean的定义、实例化依然大量依赖反射CGLIB只是把方法调用的转发优化掉了。你面试时可以说得更精确AOP的代理生成策略负责“方法级别的调用转发”而IoC的Bean创建和依赖注入依赖“类级别的元数据获取”两者底层都离不开反射提供的运行时能力但具体优化路径不同。5.3 Spring AOP的默认策略与配置方法Spring的AOP默认策略是如果目标类实现了接口优先用JDK动态代理如果没有实现接口则用CGLIB。Spring Boot 2.x之后默认策略改为始终使用CGLIB代理不管你有没有接口。这个改动的原因是CGLIB不用强制要求接口对开发者更友好。如果我想强制使用JDK代理可以在配置文件里设置spring.aop.proxy-target-classfalse想强制CGLIB就设置为true。我在实践中建议能用接口设计的模块用JDK代理就够了接口本身就是一种约束也能保持代码的清晰度没有接口的单类放心的交给CGLIB性能上也不会有明显短板。还有一个关于CGLIB的经典坑被代理类的方法如果是private或者staticCGLIB无法覆盖这些方法不会被增强。如果一个Transactional方法被同类内的另一个方法this调用事务也不会生效因为this调用走的是类本身的方法没有经过代理对象。这个问题的本质是代理机制只拦截外部对代理对象的调用。理解这一点很多所谓的Spring事务失效的神奇案例都会迎刃而解。6. 反射在JDK底层的高级用法从MethodHandle说起6.1 为什么有了反射还需要MethodHandleJDK 7加入了一个新东西java.lang.invoke.MethodHandle字面翻译是“方法句柄”。它和反射都能实现动态调用方法但设计理念不同。反射是“基于类的检查”什么都要先拿到类的完整结构再定位到具体方法笨重但直观。MethodHandle是“基于方法签名的直接调用”它更像一个函数指针你可以先定义一个类型匹配的方法句柄绑定到某个具体方法上之后直接调用。它的调用速度比反射更快因为减少了类的遍历和权限检查过程而且能被JIT编译器更好地优化。我举个例子MethodHandle的获取方式是MethodHandles.lookup().findVirtual或者findStatic然后通过bindTo绑定实例对象最后调用invoke。写法看起来和反射类似但内部机制完全不同。MethodHandle更加轻量适合在表达式引擎、规则引擎、脚本语言集成这类场景用。6.2 Lambda表达式与反射的美妙结合很多人在学习Lambda时没意识到Lambda表达式在JVM底层实际上是使用invokedynamic指令实现的而这个指令和MethodHandle是成对出现的。JVM第一次执行到Lambda表达式时会调用引导方法通过MethodHandle创建实际的函数式接口实现类。更有意思的是Lambda表达式的实现类可能有多个方法包括lambda$xxx$0这种合成方法。你在调试环境下通过LambdaMetafactory可以看到这些细节。某些工具类也会利用反射找到这些合成方法实现针对Lambda的增强比如想拿到Lambda表达式捕获的变量值可以先拿到Lambda对象对应的类再反射获取其字段。这个玩法比较烧脑但确实存在。6.3 反射与MethodHandle的对比选择从我的实践经验看反射和MethodHandle可以这样选写框架、实现通用工具时用反射更直观因为Class对象就能拿到丰富的信息包括注解、泛型、继承关系MethodHandle在这方面的信息获取能力弱一些。做高频的动态方法调用时尤其是某个方法会被上万次调用的场景用MethodHandle会有实打实的性能收益。不过MethodHandle的学习曲线比反射陡峭而且拿方法句柄的API也不如反射那么丰富。我的建议是先把反射吃透因为反射是基础理解了反射之后再学MethodHandle就是水到渠成的事情。面试时能把两者的原理区别讲清楚已经能说明你不仅会用还理解底层机制这比单纯背API更有竞争力。7. 反射常见问题与排查技巧实录这一节我把自己这些年实际遇到过的反射问题做个汇总每一个都是线上环境或者项目开发里真真切切踩过的坑照着这个清单排查能帮你省下大量时间。第一个典型问题getDeclaredField拿不到父类字段。这个前面提过但我要再强调一下因为实在是太常见了。有一次我用反射写一个通用实体校验工具需要批量校验对象的字段包括继承自基类的字段结果getDeclaredFields只返回了子类声明的字段。排查了很久才意识到getDeclaredFields本身就是“只返回当前类声明字段”的语义并不是什么Bug。解决方案是写一个循环逐层向上遍历父类直到Object为止。第二个典型问题setAccessible(true)之后仍然报权限异常。这个大概率是JDK 9以上模块系统导致的。JDK 9之后JDK内部的许多类都被封装在模块中不允许外部代码随意修改私有成员。比如你想反射修改String类的value字段在JDK 8下可以做到JDK 9之后必须加--add-opens参数否则直接抛InaccessibleObjectException。很多网上教程没有提示这个差异照搬旧教程在自己电脑上跑不通是常事。第三个典型问题反射调用方法时参数类型匹配不上。我写过一个小工具把配置文件的字符串参数转换成方法入参结果用Integer.class能匹配到方法用int.class就报NoSuchMethodException。原因就是基本类型和包装类型在反射看来是两种不同的类型。获取方法时参数类型要和方法签名完全一致包括基本类型和包装类型的区别。解决方法是先遍历方法的所有重载用名称匹配后再对参数类型做兼容性判断或者直接获取所有方法后逐个比较。第四个典型问题接口中的方法无法通过getDeclaredMethod直接找到实现类的私有方法。接口方法肯定是public的但实现类如果在其内部定义了一个同名的私有重载方法你用接口类型去反射找到的其实是接口声明的方法不是实现类的私有方法。要在实现类中查找私有方法必须以实现类的Class对象为入口去获取。这个坑我记忆犹新当时编写一个插件的拦截逻辑为了从接口类型中找到实现类的私有钩子方法费了不少劲。第五个问题更底层反射在高并发场景下使用缓存Map时不小心用了HashMap并发扩容导致CPU 100%。有一次线上服务半夜突然CPU飙升排查半天发现是一个反射工具类用了静态HashMap做方法缓存高并发下HashMap的put操作触发了无限循环。换成ConcurrentHashMap之后问题立刻消失。这里也提醒大家凡是要做全局缓存的对象并发安全一定要考虑到位。我把这些问题做成一个速查表方便大家快速定位问题现象根因解决方案拿不到父类字段getDeclaredFields只返回当前类字段逐层向上遍历父类setAccessible抛异常JDK模块系统封禁反射加--add-opens或降低模块化约束NoSuchMethodException参数类型不匹配精确匹配方法签名注意基本类型封装接口类型的反射拿不到实现类私有方法反射入口类型错了以实现类的Class对象为准缓存Map并发问题用了非线程安全集合使用ConcurrentHashMap8. 一套可以直接用的反射工具类模板讲了这么多原理和坑最后我分享一个我自己在项目中常用的反射工具类核心代码可以直接抄走改造也能帮助你把前面讲的知识点串起来。这个工具类包含字段遍历、方法调用、对象拷贝三个最常用的功能。public class ReflectUtils { /** 缓存Class对象避免重复加载 */ private static final ConcurrentHashMapString, Class? CLASS_CACHE new ConcurrentHashMap(); /** 缓存Field列表避免重复遍历 */ private static final ConcurrentHashMapClass?, ListField FIELD_CACHE new ConcurrentHashMap(); /** 缓存Method映射key为“方法名:参数类型列表” */ private static final ConcurrentHashMapClass?, MapString, Method METHOD_CACHE new ConcurrentHashMap(); public static Class? forName(String className) throws ClassNotFoundException { Class? clazz CLASS_CACHE.get(className); if (clazz ! null) { return clazz; } clazz Class.forName(className); CLASS_CACHE.put(className, clazz); return clazz; } /** 获取包括父类在内的所有字段 */ public static ListField getAllFields(Class? clazz) { return FIELD_CACHE.computeIfAbsent(clazz, c - { ListField fields new ArrayList(); Class? current c; while (current ! null current ! Object.class) { fields.addAll(Arrays.asList(current.getDeclaredFields())); current current.getSuperclass(); } return fields; }); } /** 调用目标方法自动处理私有方法和异常包装 */ public static Object invokeMethod(Object target, String methodName, Object... args) throws Throwable { Class? clazz target.getClass(); Class?[] paramTypes Arrays.stream(args) .map(Object::getClass) .toArray(Class[]::new); String key methodName : Arrays.toString(paramTypes); MapString, Method methodMap METHOD_CACHE.computeIfAbsent(clazz, c - new ConcurrentHashMap()); Method method methodMap.get(key); if (method null) { method clazz.getDeclaredMethod(methodName, paramTypes); method.setAccessible(true); methodMap.put(key, method); } try { return method.invoke(target, args); } catch (InvocationTargetException e) { throw e.getCause(); } } }这个工具类里做了几件重要的事字段获取沿父类链遍历解决了前面讲的父类字段丢失问题Method对象缓存后复用性能比每次都获取要快不少异常拆包让上层看到真正的业务异常。你在实际项目中还可以根据需求扩展加上方法入参为空时的兼容处理加上重载方法的参数匹配逻辑加上字段拷贝时的类型转换策略。根据我个人的使用体验反射工具类千万不要写完就扔一定要放在基础组件包里统一维护配合单元测试覆盖各种边界情况。反射相关的代码单测尤其重要因为异常都是运行时才暴露的编译期根本发现不了。多写几个测试用例比如父类字段获取、私有方法调用、异常拆包后面项目迭代才能放心地复用这些工具方法。这些经验都是我在实际项目中一点点积累出来的。回想起来对反射理解最深刻的阶段反而不是背API的时候而是被迫去排查那些InvocationTargetException和NoSuchMethodException的时候。当时在做一个通用数据上报组件接收各种来源的异构对象所有字段映射都靠反射一个对象几十个字段映射错了就要返工。后来我干脆自己写了一套反射工具逐渐把性能优化、异常处理、缓存策略这些细节都摸透了也算歪打正着地把反射这个知识点吃到了肚子里。你现在看完这篇文如果再遇到和反射相关的问题我建议不要把目光局限在“怎么调API”上多想一想“为什么这里要用反射”“能不能用更轻量的方案替代”把这几层问题想通了无论写代码还是面试你都不再是背八股的人而是真正理解Java运行时机制的人。
返回列表