news 2026/6/24 5:23:40

反射的定义、使用方式、优缺点和具体使用场景

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
反射的定义、使用方式、优缺点和具体使用场景

目录

1.反射的定义

2.反射的使用方式

3.反射的使用场景

3.1 主流开源框架底层(反射最核心应用场景)

1. Spring 全家桶

2. ORM 持久层框架(MyBatis/Hibernate)

3. JSON 序列化工具(Jackson/FastJSON/Gson)

3.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 全家桶

  1. IOC 容器:通过反射实例化 Bean、自动注入@Autowired依赖;
  2. AOP 切面:反射拦截目标方法,统一处理日志、权限、事务;
  3. @Transactional事务:反射读取方法上的事务注解,生成事务代理;
  4. SpringMVC 参数绑定:反射获取 Controller 方法参数,自动把请求参数封装到实体。

2. ORM 持久层框架(MyBatis/Hibernate)

  1. 数据库结果集自动封装实体:反射读取实体字段,把 SQL 查询结果赋值给对象;
  2. Mapper 动态代理:无需手写实现类,反射调用数据库操作方法;
  3. 根据实体类字段自动映射数据库表、列。

3. JSON 序列化工具(Jackson/FastJSON/Gson)

序列化对象转 JSON、JSON 反序列化为实体: 运行时通过反射遍历实体所有字段,读写属性,不需要手动调用 get/set。

3.2 通用工具类开发(业务中常用封装)

  1. 对象拷贝工具SpringBeanUtils、ApacheBeanUtils:利用反射自动匹配同名字段,实现对象复制,省去大量手动 get/set 代码。
  2. 通用 Excel/Word 导入导出(POI)通过反射读取实体字段 + 自定义注解,一套工具适配所有实体,自动映射表格列和实体属性。

3.3 注解驱动自定义开发

自定义业务注解(@操作日志@数据权限@字典翻译): 程序运行时通过反射获取类 / 方法 / 字段上的注解,读取注解配置,执行对应逻辑。 示例:自定义日志注解,切面通过反射拿到注解描述,自动记录操作人、操作内容。

3.4 动态代理 & 统一拦截处理

  1. JDK 动态代理、CGLib 底层依赖反射拦截方法;
  2. 统一做接口限流、日志、异常捕获、权限校验,无需改动原有业务代码。

3.5 插件化、动态加载、热部署

  1. JDBC 驱动加载:Class.forName()反射加载数据库驱动类;
  2. 插件架构:运行时加载外部 Jar,反射实例化插件类,实现不重启更新功能;
  3. 服务热更新、模块化动态加载。

3.6 RPC、定时任务等中间件

  1. Dubbo/OpenFeign RPC 框架:动态生成接口代理,反射调用远程接口;
  2. XXL-Job、Quartz 定时任务:配置类名 + 方法名,运行时反射执行目标业务方法。

3.7 单元测试场景

  1. JUnit/Mockito:反射调用私有方法、给私有字段赋值;
  2. 暴力访问私有成员,测试类内部私有逻辑。

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); } }

硬编码存在的问题(原文说的臃肿)

  1. 商家越来越多,if-else无限膨胀,代码一坨;
  2. 新增商家核销逻辑,必须修改WriteOffService核心代码、重新发布上线;
  3. 主业务逻辑和商家定制逻辑耦合,维护、测试成本极高。

方案 2:使用反射实现动态适配(原文说的方案)

核心思路:

  1. 配置文件建立映射:商家 ID ↔ 对应实现类全类名;
  2. 业务代码只读取配置,通过反射动态创建对应商家的核销对象;
  3. 新增商家只改配置文件,完全不用动核心业务代码

步骤 1:配置文件 shop-mapping.properties

properties:

# key=商家ID,value=核销实现类完整包名+类名 1001=com.example.shop.ShopAWriteOff 1002=com.example.shop.ShopBWriteOff 1003=com.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/else)

import 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?sharetype=blogdetail&sharerId=162213720&sharerefer=PC&sharesource=m0_64422133&spm=1011.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.ShopCWriteOff

3.加载并使用

// 一次性加载该接口的所有实现类 ServiceLoader<OrderWriteOff> loader = ServiceLoader.load(OrderWriteOff.class); // 遍历所有实现执行 for (OrderWriteOff processor : loader) { processor.writeOff(orderId); }

你的案例 和 标准 SPI 的核心区别

对比维度JDK 标准 SPI多商家核销自定义实现
规范标准JDK 官方强制约定,有固定的配置路径、文件名规则完全自定义的配置格式(properties 键值对),无统一标准
加载逻辑全量加载:一次性加载该接口的所有实现类按需加载:根据商家 ID,只加载指定的某一个实现类
映射关系纯接口 - 实现映射:只有「接口 → 实现类列表」,无业务标识业务定向映射:「商家 ID (业务标识) → 对应实现类」,精准匹配
实现工具官方封装的ServiceLoader工具类手写Class.forName()原生反射代码
适用场景框架级扩展(如 JDBC 驱动、日志实现),第三方插件接入业务层策略分发,根据业务标识选择对应逻辑
两者的核心联系
  1. 底层原理完全一致都依赖 Java 反射的「动态类加载 + 运行时实例化」能力,本质都是在编译期不绑定具体实现类,运行时动态发现并创建对象。

  2. 设计思想完全同源都遵循「面向接口编程 + 配置化解耦 + 插拔式扩展」的思路,都符合开闭原则:新增实现类不用修改核心业务代码,核心目标都是解耦调用方与具体实现。

  3. 层级关系你可以把标准 SPI 理解成「官方通用版的反射加载工具」,把你的案例理解成「针对业务场景定制的类 SPI 实现」。更像是Dubbo SPI ,Dubbo SPI 可以通过名称匹配对应实现类,这种增强版 SPI 就和案例中的「商家 ID→实现类」模式非常相似。

5.3 第三方SDK的无侵入扩展 使用到了反射

面试时回答

在接入各类第三方SDK时,部分SDK不支持定制化扩展接口,硬编码对接会出现耦合度过高的问题。此时用Java反射可以实现无侵入式扩展,比如对接第三方短信SDK时,部分平台的回调参数格式无法匹配内部系统字段,通过反射可以动态读取SDK回调的返回值,再映射到内部系统的实体类中,不用修改SDK的原有代码。这种方式既保留了SDK的原生功能,又能适配内部系统的业务规则,避免了硬编码对接带来的耦合风险。

详解:

背景设定

你公司接入了一个第三方短信服务商 SDK,有两个硬性限制:

  1. SDK 是封装好的 Jar 包,拿不到源码、不能修改 SDK 内部任何类,SDK 没有提供自定义字段转换的接口;
  2. 第三方回调返回的实体字段名,和你自己系统内部数据库实体字段名完全不匹配。
SDK 第三方回调实体(不可修改)我方内部业务实体(存数据库)
thirdPhone(第三方手机号)userPhone(业务手机号)
thirdMsgId(第三方消息 ID)msgId(业务消息 ID)
thirdSendTime(第三方发送时间)sendTime(业务发送时间)

核心矛盾

如果不用反射,只能手动硬编码挨个 get/set,耦合极高:

  • 第三方新增一个返回字段,你就要新增一行转换代码;
  • 后续换另一家短信 SDK,所有转换代码全部重写;
  • 完全绑定第三方实体,代码侵入性极强。

反射的解决方案:写一套通用转换工具,通过反射动态读取第三方对象字段、动态给我方实体赋值,全程不修改 SDK 任何代码,实现无侵入适配

5.4 单元测试使用到了反射

1. JUnit 等单元测试框架可以使用反射机制在运行时动态地获取类和方法的信息,实现自动化测试。

(详解:

JUnit 等单元测试框架预先不知道使用者创建了哪些测试类和测试方法,运行时通过反射读取类与方法的元信息,筛选出标注了@Test注解的测试方法,再借助反射相关 API 动态调用这些测试方法,以此完成自动化测试。)

2. Java反射还能解决自动化测试中的私有方法调用难题。单元测试中经常需要验证类内部私有方法的执行逻辑,但Java语法不允许直接调用私有方法,硬编码方式需要修改类的访问权限,会破坏代码封装性。用反射可以绕过访问权限校验,直接调用目标私有方法,既能完成测试覆盖,又不会影响原代码的封装结构。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/24 5:16:39

YOLO目标检测入门讲义——RoboMaster视觉篇

引言在RoboMaster的赛场上&#xff0c;机器人需要在一秒之内完成“看到敌人—识别装甲板—计算提前量—控制云台瞄准—发射弹丸”这一整套动作。这个链条的第一步&#xff0c;也是最关键的一步&#xff0c;就是视觉目标检测。传统方法依靠装甲板灯条发光的特性&#xff0c;通过…

作者头像 李华
网站建设 2026/6/24 5:16:11

教育视频摘要技术TR-EduVSum的创新与应用

1. 教育视频摘要的技术挑战与TR-EduVSum的创新价值在当今数字化教育时代&#xff0c;YouTube等平台上的教学视频数量呈爆炸式增长。以"数据结构与算法"这类计算机核心课程为例&#xff0c;单是土耳其语相关视频就超过数千小时。但学生面临一个普遍困境&#xff1a;完…

作者头像 李华
网站建设 2026/6/24 5:16:01

基于LLM多智能体框架的翼型设计风险感知与自动化实践

1. 项目概述&#xff1a;当大模型智能体遇上传统翼型设计最近和几个在航空航天院所搞气动设计的老朋友聊天&#xff0c;他们都在为一个事儿头疼&#xff1a;新项目周期压得越来越紧&#xff0c;但传统的翼型设计流程&#xff0c;从初步构型、CFD&#xff08;计算流体力学&#…

作者头像 李华
网站建设 2026/6/24 5:14:05

归一化流自适应Hermite基:用可逆神经网络提升谱方法求解奇异PDE

1. 项目概述&#xff1a;当谱方法遇上“智能”基函数在科学计算和工程仿真领域&#xff0c;求解偏微分方程是家常便饭。谱方法&#xff0c;作为一种高精度数值方法&#xff0c;因其“谱精度”&#xff08;即误差随节点数指数衰减&#xff09;的诱人特性&#xff0c;在流体力学、…

作者头像 李华
网站建设 2026/6/24 5:12:08

基于LLM多智能体与风险感知的翼型设计框架解析

1. 项目概述&#xff1a;当大模型“组团”搞设计最近在跟几个做流体和结构设计的朋友聊天&#xff0c;大家普遍有个痛点&#xff1a;传统的翼型设计流程太“硬”了。从气动性能计算、结构强度分析到制造工艺评估&#xff0c;每个环节都像一座孤岛&#xff0c;靠工程师手动传递数…

作者头像 李华
网站建设 2026/6/24 5:06:56

机器人长时程测试平台LongBench:构建稳定可靠的机器人系统

1. 项目概述&#xff1a;为什么我们需要一个“长时程”的机器人测试台&#xff1f;如果你接触过机器人开发&#xff0c;无论是工业机械臂、服务机器人还是移动底盘&#xff0c;一定对“跑个Demo”和“稳定运行8小时”之间的巨大鸿沟深有体会。在实验室里&#xff0c;一个抓取、…

作者头像 李华