在 Java 开发中,反射和动态代理常被视为 “高级特性”,它们看似抽象,却支撑着 Spring、MyBatis 等主流框架的核心功能。本文结合手写 spring 框架的实践,从 “原理” 到 “落地”,详解这两个特性如何解决实际问题,以及它们在框架设计中的不可替代性。

一、反射:打破编译期约束,实现运行时动态操作

反射(Reflection)允许程序在运行时获取类的结构(如字段、方法、注解)并动态操作,这打破了 Java “编译期确定” 的传统约束,为框架的灵活性提供了基础。在 spring 中,反射是 IoC 容器、注解解析等功能的核心实现手段。

1. 反射的核心能力与 API

反射的核心是java.lang.Class类,它代表一个类的 “元数据”,通过它可以获取类的所有信息:

核心 API作用框架中典型应用
Class.forName()加载类并返回 Class 对象从配置文件中加载类(如 XML 中的class属性)
getDeclaredConstructor()获取类的构造器动态实例化对象(如 Bean 的创建)
getDeclaredFields()获取类的所有字段(包括私有)依赖注入(如 @Autowired 字段注入)
getDeclaredMethods()获取类的所有方法(包括私有)方法调用(如 MVC 中调用控制器方法)
setAccessible(true)跳过访问权限检查(如操作私有字段 / 方法)突破封装,操作类的内部成员

2. 在 spring 中的实战应用

(1)IoC 容器:反射实现 Bean 的动态创建与依赖注入

IoC 容器的核心是 “控制反转”—— 由容器负责创建 Bean 并注入依赖,而非手动new对象。这一过程完全依赖反射:

// mini-spring中的Bean工厂实现(简化版)
public class SimpleBeanFactory {// 存储Bean定义(类名、依赖等)private Map<String, BeanDefinition> beanDefinitions = new HashMap<>();// 获取Bean:核心是反射创建对象并注入依赖public Object getBean(String beanName) throws Exception {BeanDefinition bd = beanDefinitions.get(beanName);Class<?> beanClass = Class.forName(bd.getClassName());// 1. 反射创建对象(调用无参构造器)Object bean = beanClass.getDeclaredConstructor().newInstance();// 2. 反射注入依赖(处理@Autowired字段)for (Field field : beanClass.getDeclaredFields()) {if (field.isAnnotationPresent(Autowired.class)) {// 允许操作私有字段field.setAccessible(true);// 递归获取依赖的Bean(如UserService依赖UserDao)Object dependency = getBean(field.getName());// 注入依赖field.set(bean, dependency);}}return bean;}
}

解决的核心问题

  • 无需硬编码new UserService(new UserDao()),容器通过反射动态创建对象并注入依赖,实现了 “配置驱动” 而非 “代码驱动”;
  • 支持私有字段注入(通过setAccessible(true)),无需为依赖字段暴露 setter 方法,保持类的封装性。
(2)注解解析:反射实现 @Transactional 等注解的识别

Spring 的@Transactional@RequestMapping等注解之所以能生效,本质是框架通过反射扫描类和方法上的注解,并执行对应逻辑:

// 解析@Transactional注解,判断方法是否需要事务(简化版)
public class TransactionAnnotationParser {public boolean isTransactional(Method method) {// 检查方法上是否有@Transactionalif (method.isAnnotationPresent(Transactional.class)) {return true;}// 检查类上是否有@Transactional(方法注解优先级高于类)return method.getDeclaringClass().isAnnotationPresent(Transactional.class);}// 获取注解中的传播行为配置public Propagation getPropagation(Method method) {Transactional annotation = method.getAnnotation(Transactional.class);if (annotation == null) {annotation = method.getDeclaringClass().getAnnotation(Transactional.class);}return annotation.propagation();}
}

解决的核心问题

  • 注解本身只是 “标记”,反射让框架能在运行时识别这些标记并触发逻辑(如为 @Transactional 方法创建事务代理);
  • 无需修改被注解类的代码,实现了 “无侵入” 的功能增强(如事务、日志)。

3. 反射的性能与权衡

反射因需要动态解析类结构,性能比直接调用略低(通常慢 10-100 倍),但在框架设计中,这种权衡是值得的:

  • 框架的核心价值是 “灵活性” 和 “开发效率”,反射带来的灵活性远大于性能损耗;
  • 可通过缓存优化:将反射获取的MethodField对象缓存(如 Spring 的MethodCache),避免重复解析。

二、动态代理:无侵入增强方法,支撑 AOP 核心功能

动态代理允许在运行时创建目标对象的 “代理对象”,并在目标方法执行前后插入增强逻辑(如日志、事务)。它是 AOP(面向切面编程)的技术基础,在 spring 中,通过动态代理实现了 “方法拦截” 和 “横切逻辑复用”。

1. 两种动态代理:JDK vs CGLIB

Java 中动态代理有两种主流实现,spring 会根据目标对象类型自动选择:

代理类型底层原理适用场景核心 API / 依赖
JDK 动态代理基于接口实现,生成的代理类实现目标接口目标对象实现了接口java.lang.reflect.Proxy
CGLIB 代理基于继承,生成的代理类继承目标类目标对象无接口(如 POJO)cglib库(需额外引入)
(2)JDK 动态代理实战:AOP 方法拦截

JDK 动态代理通过Proxy.newProxyInstance()创建代理对象,核心是InvocationHandler接口(定义增强逻辑):

// 1. 目标接口与实现类
public interface UserService {void saveUser(String username);
}public class UserServiceImpl implements UserService {@Overridepublic void saveUser(String username) {System.out.println("保存用户:" + username);}
}// 2. 增强逻辑:事务拦截器(实现InvocationHandler)
public class TransactionInvocationHandler implements InvocationHandler {private final Object target; // 目标对象(被代理的对象)public TransactionInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强:开启事务System.out.println("开启事务");try {// 执行目标方法Object result = method.invoke(target, args);// 后置增强:提交事务System.out.println("提交事务");return result;} catch (Exception e) {// 异常增强:回滚事务System.out.println("回滚事务");throw e;}}
}// 3. 代理工厂:创建代理对象
public class ProxyFactory {public static Object createJdkProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(),  // 目标对象实现的接口new TransactionInvocationHandler(target) // 增强逻辑);}
}// 使用示例
public class Main {public static void main(String[] args) {UserService target = new UserServiceImpl();// 创建代理对象(表面是UserService,实际是代理类)UserService proxy = (UserService) ProxyFactory.createJdkProxy(target);proxy.saveUser("张三"); // 执行时会触发事务增强}
}

执行结果

开启事务
保存用户:张三
提交事务
(3)CGLIB 代理实战:代理无接口类

当目标对象没有实现接口时(如OrderService是一个纯 POJO 类),JDK 动态代理无法使用,此时需用 CGLIB:

// 1. 无接口的目标类
public class OrderService {public void createOrder() {System.out.println("创建订单");}
}// 2. 增强逻辑:CGLIB的MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 前置增强:打印日志System.out.println("方法开始:" + method.getName());// 执行目标方法(注意:CGLIB需用proxy.invokeSuper,而非method.invoke)Object result = proxy.invokeSuper(obj, args);// 后置增强:打印耗时System.out.println("方法结束:" + method.getName());return result;}
}// 3. 代理工厂:创建CGLIB代理
public class ProxyFactory {public static Object createCglibProxy(Class<?> targetClass) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(targetClass); // 设置父类(目标类)enhancer.setCallback(new LogMethodInterceptor()); // 设置增强器return enhancer.create(); // 创建代理对象}
}// 使用示例
public class Main {public static void main(String[] args) {OrderService proxy = (OrderService) ProxyFactory.createCglibProxy(OrderService.class);proxy.createOrder(); // 执行时会触发日志增强}
}

执行结果

方法开始:createOrder
创建订单
方法结束:createOrder

2. 动态代理在 AOP 中的核心价值

AOP 的核心是 “将横切逻辑(如事务、日志)与业务逻辑分离”,动态代理是实现这一目标的关键:

  • 无侵入:业务类(如UserServiceImpl)无需修改任何代码,增强逻辑通过代理对象植入;
  • 复用性:横切逻辑(如事务管理)只需写一次,通过代理应用到多个目标类;
  • 灵活性:可动态选择是否增强(如开发环境加日志,生产环境不加),甚至动态切换增强逻辑。

3. 代理选择策略:spring 的自动适配

spring 的ProxyFactory会根据目标对象类型自动选择代理方式,逻辑如下:

public class ProxyFactory {public static Object createProxy(Object target) {// 如果目标类实现了接口,用JDK代理if (target.getClass().getInterfaces().length > 0) {return createJdkProxy(target);} else {// 否则用CGLIB代理return createCglibProxy(target.getClass());}}
}

为什么这么设计?

  • JDK 代理是 JDK 原生支持,无需额外依赖,且性能略高于 CGLIB(对接口方法调用);
  • CGLIB 能代理无接口类,弥补了 JDK 代理的局限性,但需要引入第三方库,且不能代理final类 / 方法(因基于继承)。

三、反射与动态代理:框架设计的 “黄金搭档”

反射和动态代理不是孤立的,它们在框架中往往协同工作,以spring 的 AOP 为例:

  1. 反射扫描:通过反射扫描所有类,识别带有@Aspect注解的切面类,解析@Before@After等注解的增强逻辑和切入点(如execution(* save*(..)));
  2. 动态代理:对匹配切入点的目标类,通过 JDK 或 CGLIB 创建代理对象;
  3. 反射调用:代理对象执行时,通过反射获取目标方法的注解(如@Transactional),并根据注解配置执行增强逻辑(如事务控制)。

这种组合让框架既能 “感知” 代码结构(反射),又能 “增强” 代码行为(动态代理),最终实现了 Spring “非侵入式” 的核心设计理念。

四、总结:从 “会用” 到 “理解为什么用”

反射和动态代理之所以被称为 “高级特性”,不仅因为它们的 API 复杂,更因为它们体现了 Java 的 “动态性” 思想 —— 跳出编译期的束缚,让程序在运行时拥有更大的灵活性。

在实际开发中:

  • 对于业务代码,应谨慎使用反射和动态代理(可能降低可读性,且性能损耗在高频场景下不可忽视);
  • 对于框架或通用组件(如工具类、中间件),它们是实现 “低耦合、高扩展” 的利器,值得深入掌握。

理解这两个特性的最佳方式,就是像手写 spring 一样,尝试用它们解决实际问题 —— 当你用反射实现了第一个 IoC 容器,用动态代理完成了第一个 AOP 增强时,就能真正体会到它们的魅力。

如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90894.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90894.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/90894.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 855 (Div. 3)

A. Is It a Cat? 去重&#xff0c; 把所有字符看成大写字符&#xff0c; 然后去重&#xff0c; 观察最后结果是不是“MEOW” #include <bits/stdc.h> #define int long longvoid solve() {int n;std::cin >> n;std::string ans, t;std::cin >> ans;for (int…

Scrapy选择器深度指南:CSS与XPath实战技巧

引言&#xff1a;选择器在爬虫中的核心地位在现代爬虫开发中&#xff0c;​​选择器​​是数据提取的灵魂工具。根据2023年网络爬虫开发者调查数据显示&#xff1a;​​92%​​ 的数据提取错误源于选择器编写不当熟练使用选择器的开发效率相比新手提升 ​​300%​​同时掌握CSS…

Windos服务器升级MySQL版本

Windos服务器升级MySQL版本 1.备份数据库 windows下必须以管理员身份运行命令行工具进行备份&#xff0c;如果没有配置MySQL的环境变量&#xff0c;需要进入MySQL Server 的bin目录输入指令&#xff0c; mysqldump -u root -p --all-databases > backup.sql再输入数据库密码…

告别频繁登录!Nuxt3 + TypeScript + Vue3实战:双Token无感刷新方案全解析

前言 在现代 Web 应用中&#xff0c;身份认证是保障系统安全的重要环节。传统的单 Token 认证方式存在诸多不足&#xff0c;如 Token 过期后需要用户重新登录&#xff0c;影响用户体验。本文将详细介绍如何在 Nuxt3 TypeScript Vue3 项目中实现无感刷新 Token 机制&#xff…

Linux——Redis

目录 一、Redis概念 1.1 Redis定义 1.2 Redis的特点 1.3 Redis的用途 1.4 Redis与其他数据库的对比 二、Redis数据库 三、Redis五个基本类型 3.1 字符串 3.2 列表(list) ——可以有相同的值 3.3 集合(set) ——值不能重复 3.4 哈希(hash) ——类似于Map集合 3.5 有序…

【AI大模型】部署优化量化:INT8压缩模型

INT8&#xff08;8位整数&#xff09;量化是AI大模型部署中最激进的压缩技术&#xff0c;通过将模型权重和激活值从FP32降至INT8&#xff08;-128&#xff5e;127整数&#xff09;&#xff0c;实现4倍内存压缩2-4倍推理加速&#xff0c;是边缘计算和高并发服务的核心优化手段。…

LFU 缓存

题目链接 LFU 缓存 题目描述 注意点 1 < capacity < 10^40 < key < 10^50 < value < 10^9对缓存中的键执行 get 或 put 操作&#xff0c;使用计数器的值将会递增当缓存达到其容量 capacity 时&#xff0c;则应该在插入新项之前&#xff0c;移除最不经常使…

检查输入有效性(指针是否为NULL)和检查字符串长度是否为0

检查输入有效性&#xff08;指针是否为NULL&#xff09;和检查字符串长度是否为0 这两个检查针对的是完全不同的边界情况&#xff0c;都是必要的防御性编程措施&#xff1a; 1. 空指针检查 if(!src) 目的&#xff1a;防止解引用空指针场景&#xff1a;当调用者传入 NULL 时风险…

Apache POI 的 HSSFWorkbook、SXSSFWorkbook和XSSFWorkbook三者的区别

HSSFWorkbook 专用于处理Excel 97-2003&#xff08;.xls&#xff09;格式的二进制文件。基于纯Java实现&#xff0c;所有数据存储在内存中&#xff0c;适合小规模数据&#xff08;通常不超过万行&#xff09;。内存占用较高&#xff0c;但功能完整&#xff0c;支持所有旧版Exce…

冷冻电镜重构的GPU加速破局:从Relion到CryoSPARC的并行重构算法

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生专属优惠。 一、冷冻电镜重构的算力困局 随着单粒子冷冻电镜&#xff08;cryo-EM&#xff09;分辨率突破…

算法学习笔记:16.哈希算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题

在计算机科学中&#xff0c;哈希算法&#xff08;Hash Algorithm&#xff09;是一种将任意长度的输入数据映射到固定长度输出的技术&#xff0c;其输出称为哈希值&#xff08;Hash Value&#xff09;或散列值。哈希算法凭借高效的查找、插入和删除性能&#xff0c;在数据存储、…

16018.UE4+Airsim仿真环境搭建超级详细

文章目录 1 源码下载2 下载安装软件2.1 安装 UE4 软件2.2 安装visual studio 20223 编译airsim源码4 进入AirSim工程,打开工程5 UE4 工程创建5.1 下载免费场景 CityPark,并创建工程5.2 工程编译5.2.1 将airsim 插件拷贝到 UE4工程路径中5.2.2 修改工程配置文件5.2.3 创建c++类…

Python 实战:构建 Git 自动化助手

在多项目协作、企业级工程管理或开源社区维护中&#xff0c;经常面临需要同时管理数十甚至上百个 Git 仓库的场景&#xff1a;多仓库需要统一 pull 拉取更新定期向多个项目批量 commit 和 push自动备份 Git 项目批量拉取私有仓库并管理密钥为解决这类高频、重复、机械性工作&am…

【PTA数据结构 | C语言版】出栈序列的合法性

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 给定一个最大容量为 m 的堆栈&#xff0c;将 n 个数字按 1, 2, 3, …, n 的顺序入栈&#xff0c;允许按任何顺序出栈&#xff0c;则哪些数字序列是不可能得到的&#xff1f;例如给定 m5、n7&#xf…

【LangGraph】create_react_agent 方法详细解释

create_react_agent 方法详细解释 create_react_agent 方法是一个在 LangGraph 中创建 React 代理的核心函数,接下来我们将一起探讨这个函数的作用、参数、返回值以及工作原理。 @_convert_modifier_to_prompt def create_react_agent(model: Union[str, LanguageModelLike]…

【时间之外】尘封的智能套件复活记

目录 尘封的奖品 初次触网的挫败 客服只会诱导消费 意外发现的生机 真相与反思 尘封的奖品 五年前那个蝉鸣阵阵的夏日&#xff0c;我抱着创新比赛特等奖的奖品礼盒走下领奖台时&#xff0c;绝对想不到这份荣誉会衍生出如此曲折的故事。礼盒里静静躺着的智能家居套装&…

从零开始学前端html篇1

1基本结构<!DOCTYPE html> <html><head><title>this is a good website</title></head><body><h1>hello!</h1></body> </html>运行效果如下&#xff08;编辑器提示waings:"缺少所需的 lang 特性"…

Redis Cluster 手动部署(小白的“升级打怪”成长之路)

目录 一、环境规划 二、基础环境 1、创建配置目录 2、生成配置文件 3、修改监听端口 4、修改数据目录 5、修改日志目录 6、修改PID文件目录 7、修改保护模式 8、修改进程运行模式 9、修改监听地址 10、生成集群配置 11、启动服务 三、构建集群 1、将其他节点加入…

【Java入门到精通】(三)Java基础语法(下)

一、面向对象&#xff08;类和对象&#xff09;1.1 万事万物皆对象类&#xff1a;对对象向上抽取出像的部分、公共的部分以此形成类&#xff0c;类就相当于一个模板。对象&#xff1a;模板下具体的产物可以理解为具体对象&#xff0c;对象就是一个一个具体的实例&#xff0c;就…

Java文件传输要点

Java文件传输要点 一、前端 <form action"/upload" method"post" enctype"multipart/form-data"> <!--<form action"/upload" method"post">-->姓名: <input type"text" name"username…