SpringAOP详解(二)

一、代理模式的核心概念

1. 定义

为目标对象提供 "代理类",让调用方不直接访问目标对象,而是通过代理类间接访问,从而在代理类中实现功能增强(比如日志、权限校验)。

2. 核心角色(以 "房屋租赁" 为例)
角色 对应示例 作用说明
Subject HouseSubject接口 定义目标对象和代理类的共同行为(比如 "租房")
RealSubject RealHouseSubject(房东) 目标对象(被代理的实际业务执行者)
Proxy HouseProxy(中介) 代理类,包装目标对象,在调用目标方法前后添加增强逻辑

静态代理的实现步骤(以房屋租赁为例)

1. 定义共同接口(Subject)
2. 实现目标对象(RealSubject)
3. 实现代理类(Proxy)
4. 使用代理
静态代理的核心特点
  • 提前创建 :代理类的.class文件在程序运行前就已存在(比如HouseProxy是提前写好的);
  • 功能增强:不修改目标对象代码,通过代理类实现 "附加逻辑"(符合 "开闭原则");
  • 局限性 :一个代理类只能代理一种类型的目标对象,若要代理多个类,需写多个代理类(所以 Spring AOP 实际用的是动态代理,解决这个局限性)。
和 Spring AOP 的关系

静态代理是代理模式的基础,而 Spring AOP 的底层是动态代理(运行时自动生成代理类),但核心思想一致:通过代理类包装目标对象,实现无侵入的功能增强。

二、动态代理的核心优势

相比静态代理,动态代理无需为每个目标对象写单独的代理类 ,而是在程序运行时动态生成代理对象,更灵活、更通用。

2.1JDK 动态代理的实现步骤(以房屋租赁为例)
步骤 1:准备 "干活的对象"(目标对象)
  • 目标:确定 "要被代理的实体 + 要干的活";
  • 人话:找个 "房东"(RealHouseSubject),他会干 "租房"(rentHouse 方法)这个活;
  • 代码对应
步骤 2:写 "中介的规则"(InvocationHandler)
  • 目标:定义 "中介要在房东干活前后加什么额外操作";
  • 人话:中介(JDKInvocationHandler)要在房东租房前说 "开始代理",租房后说 "代理结束";
  • 代码对应
步骤 3:生成 "中介"(动态代理对象)
  • 目标:让 JVM 自动创建 "中介",代替房东和用户打交道;
  • 人话:告诉 JVM"用房东的类加载器、房东会的活(接口)、中介的规则",生成一个中介;
  • 代码对应
步骤 4:看结果(验证逻辑)

运行后输出:

3.JDK 动态代理的关键 API
API 作用说明
InvocationHandler 方法调用处理器接口,invoke方法是代理增强逻辑的入口(参数含代理对象、目标方法、方法参数)。
Proxy.newProxyInstance 动态生成代理对象的核心方法,需传入类加载器、目标对象实现的接口、InvocationHandler 实例
4.JDK 动态代理的局限性

只能代理实现了接口的类 (因为newProxyInstance需要传入接口数组)。若要代理无接口的类,需用CGLIB 动态代理(后续内容)。

5.和 Spring AOP 的关系

Spring AOP 默认优先用JDK 动态代理 (代理有接口的类),若目标类无接口,则自动切换为CGLIB 动态代理,底层逻辑与上述一致 ------ 通过动态生成代理类实现无侵入的功能增强。

三、AOP + 代理模式核心拆解

第一层:先搞懂"目标对象"
核心逻辑

目标对象 = Spring 管理的 Bean(比如 Controller/Service) + 这个 Bean 里的业务方法(比如接口方法 / 保存用户方法)

解释

就是 "真正干活的对象 + 具体要干的活",比如:

  • 干活的对象:UserController实例(Spring 自动创建的 Bean);
  • 具体的活:getUserById(Long id)方法(查询用户的业务逻辑)。
第二层:为什么需要"代理"(AOP 的本质)
核心逻辑

不想修改目标对象的代码,但想给 "具体的活" 加额外逻辑(日志 / 权限 / 事务)→ 找个 "中间人"(代理对象)帮我们加。

解释

比如:房东(目标对象)只想 "租房"(核心活),不想管 "带看房 / 签合同"(额外活)→ 找中介(代理对象),中介帮房东加这些额外活,房东只干核心的租房。

第三层:「静态代理」vs「动态代理」(代理的两种方式)
类型 核心逻辑 人话解释
静态代理 提前写死代理类(一个目标对象对应一个代理类) 给房东 A 写个专属中介,给房东 B 写另一个专属中介
动态代理 运行时自动生成代理类(所有目标对象共用一套逻辑) 一个中介公司,能代理所有房东,不用单独写中介
关键:Spring AOP 用的是"动态代理"
  • JDK 动态代理:代理有接口的类(比如 Controller/Service 都实现了接口);
  • CGLIB 动态代理:代理没接口的类(兜底方案)。
第四层:Spring AOP 的核心组件(代理的 "包装")

把动态代理的逻辑封装成 3 个好懂的组件,不用自己写代理类:

1. 切面类(@Aspect + @Component)
  • 核心逻辑:告诉 Spring "这是一个 AOP 增强类",并交给 Spring 管理;
  • 人话:就是 "中介公司的规则手册",定义了 "要帮哪些房东干活 + 要加哪些额外活"。
2. 切点(execution/@annotation)
  • 核心逻辑:划定 "要增强哪些目标对象的方法"(哪些房东要被代理);
  • 人话:规则 1:"只代理朝阳区的房东"(execution 匹配包 / 类);规则 2:"只代理贴了"急租"标签的房东"(@annotation 匹配注解)。
3. 通知(@Before/@Around 等)
  • 核心逻辑:定义 "要加的额外活"(什么时候加、加什么);
  • 人话:规则:"带客户看房(@Before)→ 房东租房 → 收中介费(@After)"。
第五层:完整执行流程

以 "访问 UserController 的 getUserById 方法" 为例:

  1. 你调用userController.getUserById(1) → 实际调用的是 Spring 生成的"代理对象";
  2. 代理对象先执行通知逻辑(比如 @Before 打印日志);
  3. 代理对象调用"目标对象"的getUserById(1)(真正的查询逻辑);
  4. 代理对象再执行后续通知(比如 @After 统计耗时);
  5. 返回结果给你。
核心总结(一句话)

Spring AOP = 动态代理(自动生成中介) + 切面(中介规则) + 切点(选房东) + 通知(加额外活),最终实现 "不修改目标对象代码,却能增强其方法"。

四、CGLIB 动态代理

1.先搞懂:CGLIB 是干嘛的?

JDK 动态代理只能代理 "有接口的类",而CGLIB 是用来代理 "没有接口的类" (比如普通的 Java 类),它的原理是动态生成目标类的子类,通过子类来实现增强。

2.CGLIB 动态代理的 3 步核心动作
步骤 1:准备 "要被代理的类"(不用接口!)
  • 目标:找一个 "没实现接口的类"(比如普通的房东类);
  • 人话 :房东类RealHouseSubject没实现任何接口,但会干 "租房" 这个活;
  • 代码对应
步骤 2:写 "增强规则"(MethodInterceptor)
  • 目标:定义 "代理要在房东干活前后加什么操作";
  • 人话:中介(CGLIBInterceptor)要在房东租房前说 "开始代理",租房后说 "代理结束";
  • 代码对应
步骤 3:生成 "代理子类"(Enhancer.create)
  • 目标:让 CGLIB 动态生成目标类的子类(代理类);
  • 人话:告诉 CGLIB"要代理的类 + 增强规则",生成一个 "房东的子类(中介)";
  • 代码对应
步骤 4:看结果(和 JDK 代理一样)

运行后输出:

3.CGLIB 和 JDK 代理的核心对比
维度 JDK 动态代理 CGLIB 动态代理
代理条件 必须实现接口 可以代理无接口的类(生成子类)
核心 API InvocationHandler + Proxy MethodInterceptor + Enhancer
底层原理 基于接口的动态代理 基于子类的动态代理
Spring AOP 场景 优先用(代理有接口的 Bean) 兜底用(代理无接口的 Bean)
4.总结 CGLIB

CGLIB 是 JDK 代理的 "补位方案"------ 专门解决 "无接口类的代理问题",通过生成目标类的子类,实现和 JDK 代理一样的增强效果,Spring AOP 会自动在 JDK 和 CGLIB 之间切换。

五、Spring AOP 的源码

1.Spring AOP 的核心逻辑:谁来生成代理?

Spring AOP 生成代理的核心类是AnnotationAwareAspectJAutoProxyCreator,它的父类AbstractAutoProxyCreator里的createProxy方法,是生成代理的 "入口"。

2.生成代理的 3 步核心流程(源码逻辑)
步骤 1:创建 "代理工厂"(ProxyFactory)
  • 目标:封装代理的配置信息(比如用 JDK 还是 CGLIB);
  • 代码对应
步骤 2:决定 "用 JDK 还是 CGLIB"(核心判断)

通过proxyTargetClass这个配置项,决定代理方式:

proxyTargetClass 值 目标对象情况 代理方式
false(默认) 实现了接口 JDK 代理
false(默认) 没实现接口 CGLIB 代理
true 不管有没有接口 CGLIB 代理
步骤 3:生成代理对象(调用 JDK/CGLIB 的 API)

代理工厂通过getProxy方法,最终调用我们之前学的 JDK/CGLIB API 生成代理:

  • JDK 代理 :调用Proxy.newProxyInstance(对应JdkDynamicAopProxy类);
  • CGLIB 代理 :调用Enhancer.create(对应CglibAopProxy类);

3.Spring Boot 中的关键配置

  • Spring Boot 2.x 开始,默认用 CGLIB 代理 (相当于默认proxyTargetClass=true);
  • 若要改回 JDK 代理,需在配置文件加:
4.一句话总结 Spring AOP 源码逻辑

Spring AOP 通过ProxyFactory封装配置,根据proxyTargetClass和 "目标类是否有接口",自动选择 JDK/CGLIB 代理,最终调用对应的动态代理 API 生成代理对象 ------我们之前学的 JDK/CGLIB,就是 Spring AOP 的底层实现

相关推荐
iiiiii112 小时前
TD(λ),资格迹(Eligibility Traces)与时序差分学习的统一
人工智能·学习·机器学习·强化学习·rl
廋到被风吹走2 小时前
【Spring】Spring Cache 深度解析
java·后端·spring
走在路上的菜鸟3 小时前
Android学Flutter学习笔记 第一节 Android视角认知Flutter(View,intent,Async UI)
android·学习·flutter
坚持学习前端日记3 小时前
个人运营小网站的最佳策略
java·学习·程序人生·职场和发展·创业创新
崇山峻岭之间3 小时前
Matlab学习记录20
开发语言·学习·matlab
七夜zippoe3 小时前
响应式编程基石 Project Reactor源码解读
java·spring·flux·响应式编程·mono·订阅机制
笔夏3 小时前
【安卓学习之myt】adb常用命令
android·学习·adb
IT 行者3 小时前
Spring Framework 6.x 异常国际化完全指南:让错误信息“说“多国语言
java·后端·spring·异常处理·problemdetail·国际化i18n
d111111111d4 小时前
配置STM32F411CEU6的系统时钟-避免芯片内核锁死
笔记·stm32·单片机·嵌入式硬件·学习