目录1.反射的定义2.反射的使用方式3.反射的使用场景3.1 主流开源框架底层反射最核心应用场景1. Spring 全家桶2. ORM 持久层框架MyBatis/Hibernate3. JSON 序列化工具Jackson/FastJSON/Gson3.2 通用工具类开发业务中常用封装3.3 注解驱动自定义开发3.4 动态代理 统一拦截处理3.5 插件化、动态加载、热部署3.6 RPC、定时任务等中间件3.7 单元测试场景4.反射的优缺点5.反射在项目中的具体使用场景5.1 自定义注解 使用到了反射5.2 基于配置动态加载业务实现类 使用到了反射5.2.1 详细讲解场景简化方案 1不用反射硬编码 if/else痛点巨大方案 2使用反射实现动态适配原文说的方案上面核销场景代码改为标准 SPI 代码示例了解两者的核心联系5.3 第三方SDK的无侵入扩展 使用到了反射5.4 单元测试使用到了反射1.反射的定义反射是 Java 在运行时动态获取类信息、操作对象成员的技术。Java 编译时确定类型运行时通过反射 API 可以- 动态获取类的结构方法、字段、注解等- 动态调用方法、访问字段- 动态创建对象2.反射的使用方式1.先获取 Class 对象有了 Class 对象就可以进行类的初始化操作了Class? clazz Class.forName(com.mianshiya.MyClass);2.创建对象实例// 获取无参构造器 Constructor? constructor clazz.getConstructor(); // 通过构造器创建对象实例 Object obj constructor.newInstance();3.获取对象的属性getField()方法只能获取类的public字段的值// 这里假设MyClass有一个名为myField的字段 Field field clazz.getField(myField);getDeclaredField获取类的全部字段private/protected/public/ 默认权限// 这里假设MyClass有一个名为myField的字段 Field field clazz.getField(myField);4.调用方法**// 获取指定类的指定方法** // clazz类的Class对象表示要获取方法的类 // myMethod要获取的方法名 // String.class方法的参数类型表示myMethod方法有一个String类型的参数 Method method clazz.getMethod(myMethod, String.class); **// 调用获取到的方法** // obj要调用方法的对象即myMethod方法所属对象的实例 // param调用方法时传入的参数值表示将字符串param作为参数传递给myMethod方法 // result方法调用后的返回值表示myMethod方法执行后返回的结果其类型为Object可根据实际情况进行类型转换 Object result method.invoke(obj, param);5.获取注解类对象实例其实的准确来说是代理对象因为注解的底层原理是动态代理method.getAnnotation(Transactional.class)3.反射的使用场景反射核心价值运行时动态操作类、方法、字段、注解不用在编译期写死调用逻辑绝大多数中间件、框架底层都依赖反射普通业务开发很少手写原生反射。3.1 主流开源框架底层反射最核心应用场景1. Spring 全家桶IOC 容器通过反射实例化 Bean、自动注入Autowired依赖AOP 切面反射拦截目标方法统一处理日志、权限、事务Transactional事务反射读取方法上的事务注解生成事务代理SpringMVC 参数绑定反射获取 Controller 方法参数自动把请求参数封装到实体。2. ORM 持久层框架MyBatis/Hibernate数据库结果集自动封装实体反射读取实体字段把 SQL 查询结果赋值给对象Mapper 动态代理无需手写实现类反射调用数据库操作方法根据实体类字段自动映射数据库表、列。3. JSON 序列化工具Jackson/FastJSON/Gson序列化对象转 JSON、JSON 反序列化为实体 运行时通过反射遍历实体所有字段读写属性不需要手动调用 get/set。3.2 通用工具类开发业务中常用封装对象拷贝工具SpringBeanUtils、ApacheBeanUtils利用反射自动匹配同名字段实现对象复制省去大量手动 get/set 代码。通用 Excel/Word 导入导出POI通过反射读取实体字段 自定义注解一套工具适配所有实体自动映射表格列和实体属性。3.3 注解驱动自定义开发自定义业务注解操作日志、数据权限、字典翻译 程序运行时通过反射获取类 / 方法 / 字段上的注解读取注解配置执行对应逻辑。 示例自定义日志注解切面通过反射拿到注解描述自动记录操作人、操作内容。3.4 动态代理 统一拦截处理JDK 动态代理、CGLib 底层依赖反射拦截方法统一做接口限流、日志、异常捕获、权限校验无需改动原有业务代码。3.5 插件化、动态加载、热部署JDBC 驱动加载Class.forName()反射加载数据库驱动类插件架构运行时加载外部 Jar反射实例化插件类实现不重启更新功能服务热更新、模块化动态加载。3.6 RPC、定时任务等中间件Dubbo/OpenFeign RPC 框架动态生成接口代理反射调用远程接口XXL-Job、Quartz 定时任务配置类名 方法名运行时反射执行目标业务方法。3.7 单元测试场景JUnit/Mockito反射调用私有方法、给私有字段赋值暴力访问私有成员测试类内部私有逻辑。4.反射的优缺点Java 反射机制的优点可以动态地获取类的信息不需要在编译时就知道类的信息。可以动态地创建对象不需要在编译时就知道对象的类型。可以动态地调用对象的属性和方法可以在运行时动态地改变对象的行为。Java 反射机制的缺点由于反射是动态的所以它的运行效率较低不如直接调用方法或属性。由于反射是动态的所以它会破坏 Java 的封装性可能会使代码变得复杂和不稳定。5.反射在项目中的具体使用场景面试官你项目中使用过反射吗5.1 自定义注解 使用到了反射面试回答我项目中在自定义分布式锁的DistributeLock注解时切面类使用到了反射它用 Around 通知拦截所有带 DistributeLock 注解的方法拦截后通过反射即method.getAnnotation(DistributeLock.class) 方法获取注解类对象并读取注解里配置的 key、过期时间等参数再根据这些参数去加锁。说到这其实和 Spring 的 Transactional 原理是一样的都是基于反射加上 AOP 切面机制。Spring 扫描到 Transactional 注解后创建代理对象方法调用时代理拦截通过反射读取注解配置再决定是开启事务还是回滚。所以像 Transactional、Cacheable 这些声明式注解底层都是这个套路——反射负责读注解AOP 负责拦截和织入逻辑。掌握这个共性之后学任何声明式注解都很快。5.2 基于配置动态加载业务实现类 使用到了反射面试回答SaaS项目都会用反射实现多租户业务的动态适配。比如电商SaaS平台中不同品牌商家会定制专属的订单核销逻辑硬编码方式需要为每个商家单独编写分支判断代码随着商家数量增加主业务代码会变得臃肿不堪。用Java反射实现的话只需要在配置文件中登记商家对应的实现类路径系统启动时自动加载对应类就能动态调用核销逻辑不用修改主业务代码。这样不仅能减少重复编码还可以让主业务逻辑保持简洁清晰便于后续迭代维护。5.2.1 详细讲解场景简化平台是一套电商 SaaS 系统给很多商家共用 不同商家订单核销规则不一样商家 A核销必须校验会员等级商家 B核销必须校验专属优惠券商家 C只简单校验库存即可方案 1不用反射硬编码 if/else痛点巨大定义统一核销接口// 统一核销规范 public interface OrderWriteOff { void writeOff(Long orderId); }各个商家实现类// 商家A核销逻辑 public class ShopAWriteOff implements OrderWriteOff{ Override public void writeOff(Long orderId) { System.out.println(商家A校验会员等级后核销订单); } } // 商家B核销逻辑 public class ShopBWriteOff implements OrderWriteOff{ Override public void writeOff(Long orderId) { System.out.println(商家B校验专属优惠券后核销订单); } } // 商家C核销逻辑 public class ShopCWriteOff implements OrderWriteOff{ Override public void writeOff(Long orderId) { System.out.println(商家C仅校验库存后核销订单); } }核心业务代码硬编码写法public class WriteOffService { // 根据商家ID执行核销 public void doWriteOff(Long shopId, Long orderId) { OrderWriteOff processor null; if (shopId 1001L) { processor new ShopAWriteOff(); } else if (shopId 1002L) { processor new ShopBWriteOff(); } else if (shopId 1003L) { processor new ShopCWriteOff(); } // 以后每入驻一个新商家就要在这里新增else if分支 processor.writeOff(orderId); } }硬编码存在的问题原文说的臃肿商家越来越多if-else无限膨胀代码一坨新增商家核销逻辑必须修改WriteOffService核心代码、重新发布上线主业务逻辑和商家定制逻辑耦合维护、测试成本极高。方案 2使用反射实现动态适配原文说的方案核心思路配置文件建立映射商家 ID ↔ 对应实现类全类名业务代码只读取配置通过反射动态创建对应商家的核销对象新增商家只改配置文件完全不用动核心业务代码。步骤 1配置文件 shop-mapping.propertiesproperties:# key商家IDvalue核销实现类完整包名类名 1001com.example.shop.ShopAWriteOff 1002com.example.shop.ShopBWriteOff 1003com.example.shop.ShopCWriteOff步骤 2反射工具根据类名动态生成对象public class ReflectUtil { // 根据类全限定名反射创建实例 public static Object getInstance(String className) throws Exception { // 反射第一步获取Class对象 Class? clazz Class.forName(className); // 反射第二步无参构造实例化对象 return clazz.newInstance(); } }步骤 3改造后的业务核销服务无任何 if/elseimport java.util.Properties; import java.io.InputStream; public class WriteOffService { // 加载商家与实现类映射配置 private Properties loadShopConfig() throws Exception{ // 1. 创建空的配置容器 Properties prop new Properties(); // 2. 打开配置文件的读取流 InputStream is this.getClass().getResourceAsStream(shop-mapping.properties); // 3. 把文件内容解析到prop对象中 prop.load(is); // 4. 返回装载好配置的对象 return prop; } // 对外提供核销功能入口 public void doWriteOff(Long shopId, Long orderId) throws Exception { // 1. 读取全部商家配置映射 Properties config loadShopConfig(); // 2. 根据商家ID拿到对应核销实现类的全路径 String classPath config.getProperty(shopId.toString()); if (classPath null) { throw new RuntimeException(该商家无核销配置); } // 3. 反射动态创建对应商家的核销对象 OrderWriteOff processor (OrderWriteOff) ReflectUtil.getInstance(classPath); // 4. 执行当前商家专属核销逻辑 processor.writeOff(orderId); } }调用测试public class TestMain { public static void main(String[] args) throws Exception { WriteOffService service new WriteOffService(); service.doWriteOff(1001L, 100001L); // 自动执行ShopA逻辑 service.doWriteOff(1002L, 100002L); // 自动执行ShopB逻辑 } }这里的设计优点类似SPI机制但不是SPI机制介绍SPI机制-CSDN博客https://blog.csdn.net/m0_64422133/article/details/162213720?sharetypeblogdetailsharerId162213720sharereferPCsharesourcem0_64422133spm1011.2480.3001.8118上面核销场景代码改为标准 SPI 代码示例了解1.定义统一接口public interface OrderWriteOff { void writeOff(Long orderId); }2.按 SPI 规范创建配置文件 文件路径resources/META-INF/services/com.example.OrderWriteOff文件内容com.example.shop.ShopAWriteOff com.example.shop.ShopBWriteOff com.example.shop.ShopCWriteOff3.加载并使用// 一次性加载该接口的所有实现类 ServiceLoaderOrderWriteOff loader ServiceLoader.load(OrderWriteOff.class); // 遍历所有实现执行 for (OrderWriteOff processor : loader) { processor.writeOff(orderId); }你的案例 和 标准 SPI 的核心区别对比维度JDK 标准 SPI多商家核销自定义实现规范标准JDK 官方强制约定有固定的配置路径、文件名规则完全自定义的配置格式properties 键值对无统一标准加载逻辑全量加载一次性加载该接口的所有实现类按需加载根据商家 ID只加载指定的某一个实现类映射关系纯接口 - 实现映射只有「接口 → 实现类列表」无业务标识业务定向映射「商家 ID (业务标识) → 对应实现类」精准匹配实现工具官方封装的ServiceLoader工具类手写Class.forName()原生反射代码适用场景框架级扩展如 JDBC 驱动、日志实现第三方插件接入业务层策略分发根据业务标识选择对应逻辑两者的核心联系底层原理完全一致都依赖 Java 反射的「动态类加载 运行时实例化」能力本质都是在编译期不绑定具体实现类运行时动态发现并创建对象。设计思想完全同源都遵循「面向接口编程 配置化解耦 插拔式扩展」的思路都符合开闭原则新增实现类不用修改核心业务代码核心目标都是解耦调用方与具体实现。层级关系你可以把标准 SPI 理解成「官方通用版的反射加载工具」把你的案例理解成「针对业务场景定制的类 SPI 实现」。更像是Dubbo SPI Dubbo SPI 可以通过名称匹配对应实现类这种增强版 SPI 就和案例中的「商家 ID→实现类」模式非常相似。5.3 第三方SDK的无侵入扩展 使用到了反射面试时回答在接入各类第三方SDK时部分SDK不支持定制化扩展接口硬编码对接会出现耦合度过高的问题。此时用Java反射可以实现无侵入式扩展比如对接第三方短信SDK时部分平台的回调参数格式无法匹配内部系统字段通过反射可以动态读取SDK回调的返回值再映射到内部系统的实体类中不用修改SDK的原有代码。这种方式既保留了SDK的原生功能又能适配内部系统的业务规则避免了硬编码对接带来的耦合风险。详解背景设定你公司接入了一个第三方短信服务商 SDK有两个硬性限制SDK 是封装好的 Jar 包拿不到源码、不能修改 SDK 内部任何类SDK 没有提供自定义字段转换的接口第三方回调返回的实体字段名和你自己系统内部数据库实体字段名完全不匹配。SDK 第三方回调实体不可修改我方内部业务实体存数据库thirdPhone第三方手机号userPhone业务手机号thirdMsgId第三方消息 IDmsgId业务消息 IDthirdSendTime第三方发送时间sendTime业务发送时间核心矛盾如果不用反射只能手动硬编码挨个 get/set耦合极高第三方新增一个返回字段你就要新增一行转换代码后续换另一家短信 SDK所有转换代码全部重写完全绑定第三方实体代码侵入性极强。反射的解决方案写一套通用转换工具通过反射动态读取第三方对象字段、动态给我方实体赋值全程不修改 SDK 任何代码实现无侵入适配。5.4 单元测试使用到了反射1. JUnit 等单元测试框架可以使用反射机制在运行时动态地获取类和方法的信息实现自动化测试。详解JUnit 等单元测试框架预先不知道使用者创建了哪些测试类和测试方法运行时通过反射读取类与方法的元信息筛选出标注了Test注解的测试方法再借助反射相关 API 动态调用这些测试方法以此完成自动化测试。2. Java反射还能解决自动化测试中的私有方法调用难题。单元测试中经常需要验证类内部私有方法的执行逻辑但Java语法不允许直接调用私有方法硬编码方式需要修改类的访问权限会破坏代码封装性。用反射可以绕过访问权限校验直接调用目标私有方法既能完成测试覆盖又不会影响原代码的封装结构。