当前位置: 首页 > news >正文

如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)


目录

1.前言

插播一条消息~

2.正文

2.1代理模式

2.1.1静态代理

2.1.1.1代码实现示例:房屋中介代理场景

2.1.1.2静态代理的优缺点分析

2.1.2动态代理

2.1.2.1JDK动态代理:基于接口的代理实现

2.1.2.2CGLIB动态代理:基于继承的代理实现

2.1.2.3JDK与CGLIB动态代理的对比分析

2.2Spring AOP源码剖析(了解即可)

2.2.1核心类体系:从注解处理到代理创建

2.2.2代理创建的核心流程(简化版)

2.2.3核心原理总结

3.小结


1.前言

在《Spring AOP原理深度解析(上篇)》中,我们重点探讨了Spring AOP的应用层面,包括通过@Aspect注解定义切面、使用@Before @After @Around等通知类型实现横切逻辑,以及如何通过切入点表达式精确匹配目标方法。这些实践帮助开发者快速掌握了AOP在日志记录、事务管理、权限控制等场景的应用技巧。然而,对于框架使用者而言,知其然更要知其所以然——理解AOP背后的实现机制,不仅能提升问题排查能力,更能在复杂业务场景中设计出更优雅的解决方案。

Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。

阅读提示:建议读者具备Java反射机制、设计模式基础概念,以及Spring IoC容器的基本认知。文中涉及的源码分析基于Spring Framework 5.3.x版本,核心代码片段均经过简化处理以突出关键逻辑。


插播一条消息~

🔍十年经验淬炼 · 系统化AI学习平台推荐

系统化学习AI平台https://www.captainbed.cn/scy/

  • 📚 完整知识体系:从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
  • 💻 实战为王:每小节配套可运行代码案例(提供完整源码)
  • 🎯 零基础友好:用生活案例讲解算法,无需担心数学/编程基础

🚀 特别适合

  • 想系统补强AI知识的开发者
  • 转型人工智能领域的从业者
  • 需要项目经验的学生

2.正文

2.1代理模式

2.1.1静态代理

静态代理是 AOP 思想的基础实现方式,其核心是通过代理类真实业务对象进行包装,在不修改原有业务逻辑的前提下实现功能增强。这种模式遵循"结构-代码-优缺点"的分析框架,能够清晰展现代理模式的设计本质。

静态代理的标准结构与调用路径:

静态代理模式包含三个核心组成部分:

  1. 抽象主题(Subject):定义业务方法的接口,规定代理类与真实主题的统一行为规范。
  2. 真实主题(Real Subject):实现抽象主题接口,包含具体的业务逻辑实现。
  3. 代理类(Proxy):同样实现抽象主题接口,内部持有真实主题的引用,在调用真实主题方法前后插入增强逻辑。

Client 端通过代理类与真实主题交互,调用路径为:Client → 代理类方法 → 增强逻辑 → 真实主题方法 → 增强逻辑(可选)。这种结构确保了业务逻辑与增强逻辑的分离,符合单一职责原则。

2.1.1.1代码实现示例:房屋中介代理场景

以房屋交易场景为例,通过代码直观展示静态代理的工作机制。假设需要为房屋买卖(saleHouse)和租赁(rentHouse)业务添加中介的前置审核逻辑:

1. 抽象主题接口(HouseSubject)

// 定义房屋交易业务接口
public interface HouseSubject {void saleHouse();  // 房屋出售业务void rentHouse();  // 房屋租赁业务
}

2. 真实主题类(RealHouseSubject)

// 实现具体的房屋交易业务
public class RealHouseSubject implements HouseSubject {@Overridepublic void saleHouse() {System.out.println("业主执行房屋出售流程:签订合同 → 办理过户");}@Overridepublic void rentHouse() {System.out.println("业主执行房屋租赁流程:签订租约 → 交付房屋");}
}

3. 代理类(HouseProxy)

// 中介代理类,增强房屋交易流程
public class HouseProxy implements HouseSubject {private HouseSubject realSubject;  // 持有真实主题引用// 通过构造器注入真实主题实例public HouseProxy(HouseSubject realSubject) {this.realSubject = realSubject;}@Overridepublic void saleHouse() {// 前置增强逻辑:中介审核房源System.out.println("[中介] 审核房屋产权证明 → 评估市场价格 → 发布出售信息");// 调用真实主题业务方法realSubject.saleHouse();// 后置增强逻辑:中介跟进交易System.out.println("[中介] 协助办理水电过户 → 交易归档\n");}@Overridepublic void rentHouse() {// 前置增强逻辑:中介筛选租客System.out.println("[中介] 核实租客身份 → 签订租赁担保协议");// 调用真实主题业务方法realSubject.rentHouse();// 后置增强逻辑:中介定期巡检System.out.println("[中介] 每月房屋状况巡检 → 租金代收\n");}
}

4. Client 调用示例

public class Client {public static void main(String[] args) {// 创建真实业务对象HouseSubject realHouse = new RealHouseSubject();// 创建代理对象并注入真实业务对象HouseProxy proxy = new HouseProxy(realHouse);// 通过代理执行房屋出售proxy.saleHouse();// 通过代理执行房屋租赁proxy.rentHouse();}
}

执行输出

[中介] 审核房屋产权证明 → 评估市场价格 → 发布出售信息
业主执行房屋出售流程:签订合同 → 办理过户
[中介] 协助办理水电过户 → 交易归档[中介] 核实租客身份 → 签订租赁担保协议
业主执行房屋租赁流程:签订租约 → 交付房屋
[中介] 每月房屋状况巡检 → 租金代收

上述代码中,HouseProxy 通过构造器接收 RealHouseSubject 实例,在重写的业务方法中严格遵循"增强逻辑 → 真实方法调用 → 增强逻辑"的执行顺序,实现了中介服务对核心业务的无侵入增强。

2.1.1.2静态代理的优缺点分析

优点:简单直观,易于实现

静态代理的设计逻辑清晰,代理类与真实主题的关系在编译期即确定,代码结构透明,调试与维护成本低,适合增强逻辑固定且业务方法较少的场景。

核心优势:通过显式代码实现增强逻辑,执行流程可直接追溯,无需额外依赖反射或字节码技术,初学者易于理解其工作原理。

缺点:硬编码增强逻辑,灵活性差

静态代理的局限性主要体现在三个方面:

  • 增强逻辑硬编码:所有增强逻辑需在代理类中手动编写,若需修改增强规则(如调整中介审核流程),必须直接修改代理类代码。
  • 方法级重复劳动:真实主题每新增一个业务方法(如 leaseHouse()),代理类必须同步实现该方法并嵌入增强逻辑,导致代码冗余。
  • 类爆炸风险:每个真实主题需对应一个代理类,当业务对象增多时,代理类数量呈线性增长,增加系统维护复杂度。

这些缺陷使得静态代理难以应对频繁变化的增强需求大规模业务场景,从而催生出动态代理技术作为更优解。

总结:

静态代理作为 AOP 的基础实现,通过"接口-真实主题-代理类"的三层结构实现了业务逻辑与增强逻辑的分离,但其编译期固定的代理关系和硬编码增强逻辑导致灵活性不足。这种局限性恰恰为动态代理的出现提供了技术演进的动力,后者通过运行时动态生成代理类,有效解决了静态代理的扩展性问题。

2.1.2动态代理

动态代理是Spring AOP实现的核心技术,通过在运行时动态生成代理对象实现方法增强。根据实现机制的不同,主流方案可分为JDK动态代理CGLIB动态代理,二者在实现原理、适用场景上存在显著差异。

2.1.2.1JDK动态代理:基于接口的代理实现

JDK动态代理是Java原生支持的代理机制,其核心实现基于接口,要求被代理类必须实现至少一个接口,代理类通过实现相同接口完成对目标方法的增强。

实现原理与核心组件:

JDK动态代理的实现依赖java.lang.reflect.Proxy类和InvocationHandler接口。代理类在运行时由Proxy类动态生成,该类会实现目标接口,并将方法调用转发给InvocationHandlerinvoke方法处理。

关键代码示例(JDKInvocation实现):

public class JDKInvocationHandler implements InvocationHandler {private final Object target; // 目标对象public JDKInvocationHandler(Object target) {this.target = target;}/*** 代理方法调用的转发入口* @param proxy 生成的代理对象* @param method 目标方法* @param args 方法参数* @return 方法执行结果*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1. 增强逻辑(前置处理)System.out.println("JDK Proxy: 执行前置增强");// 2. 调用目标方法Object result = method.invoke(target, args);// 3. 增强逻辑(后置处理)System.out.println("JDK Proxy: 执行后置增强");return result;}// 创建代理对象public static Object createProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器:用于加载动态生成的代理类target.getClass().getInterfaces(),   // 目标接口数组:代理类需实现的接口new JDKInvocationHandler(target)     // InvocationHandler实例:处理代理逻辑);}
}

执行流程解析

JDK动态代理的方法调用流程可概括为:

  1. 代理对象调用方法:客户端调用代理对象的接口方法;
  2. 转发至InvocationHandler:代理类将调用转发给InvocationHandlerinvoke方法;
  3. 执行增强与目标方法:在invoke方法中,先执行前置增强逻辑,再通过反射调用目标对象的方法(method.invoke(target, args)),最后执行后置增强逻辑;
  4. 返回结果:将目标方法的执行结果返回给客户端。

核心参数说明invoke方法的三个参数需重点理解:

  • proxy:动态生成的代理对象本身(注意避免在增强逻辑中使用proxy调用方法,否则会导致无限递归);
  • method:当前被调用的目标方法(Method对象);
  • args:方法入参数组(若方法无参数则为null)。

2.1.2.2CGLIB动态代理:基于继承的代理实现

CGLIB(Code Generation Library)是基于字节码生成技术的第三方库,其实现原理为继承目标类,通过生成目标类的子类作为代理类,从而实现对非接口类的代理。

实现原理与核心组件

CGLIB的核心是Enhancer类(用于生成代理类)和MethodInterceptor接口(用于定义增强逻辑)。代理类通过继承目标类并重写其方法,将方法调用转发给MethodInterceptorintercept方法。

关键代码示例(CGlibMethodInterceptor实现):

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class CGlibMethodInterceptor implements MethodInterceptor {private final Object target; // 目标对象public CGlibMethodInterceptor(Object target) {this.target = target;}/*** 代理方法调用的拦截入口* @param obj 生成的代理对象* @param method 目标方法* @param args 方法参数* @param methodProxy 方法代理对象(用于高效调用父类方法)* @return 方法执行结果*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 1. 增强逻辑(前置处理)System.out.println("CGLIB Proxy: 执行前置增强");// 2. 调用目标方法(推荐使用methodProxy.invokeSuper提高性能)Object result = methodProxy.invokeSuper(obj, args); // 等价于:method.invoke(target, args),但invokeSuper性能更优// 3. 增强逻辑(后置处理)System.out.println("CGLIB Proxy: 执行后置增强");return result;}// 创建代理对象public static Object createProxy(Class<?> targetClass) {return Enhancer.create(targetClass,                // 目标类Class对象:指定被代理的类new CGlibMethodInterceptor() // MethodInterceptor实例:处理代理逻辑);}
}

执行流程解析

CGLIB动态代理的方法调用流程与JDK代理类似,但底层实现不同:

  1. 代理对象调用方法:客户端调用代理对象的方法(继承自目标类);
  2. 转发至MethodInterceptor:代理类重写的方法会将调用转发给MethodInterceptorintercept方法;
  3. 执行增强与目标方法:在intercept方法中,先执行增强逻辑,再通过methodProxy.invokeSuper(obj, args)调用目标类的方法(避免反射,性能更优),最后执行后置增强;
  4. 返回结果:将执行结果返回给客户端。

核心参数说明intercept方法比JDK的invoke多一个methodProxy参数:

  • methodProxy:CGLIB生成的方法代理对象,可通过invokeSuper(obj, args)直接调用目标类的方法,性能优于反射调用(method.invoke)。
2.1.2.3JDK与CGLIB动态代理的对比分析

为清晰区分二者的差异,以下从实现基础、适用场景、性能等维度进行对比:

对比维度JDK动态代理CGLIB动态代理
实现基础基于接口(代理类实现目标接口)基于继承(代理类继承目标类)
代理目标只能代理实现接口的类可代理非final类(不能代理final类/方法)
核心组件InvocationHandler接口MethodInterceptor接口
性能方法调用基于反射,效率较低(JDK 8后有所优化)基于字节码生成,调用通过methodProxy,效率更高
优点1. 原生支持,无需依赖第三方库

2. 代码简洁
1. 可代理类(无需接口)

2. 性能更优
缺点1. 仅能代理接口,局限性大

2. 反射调用开销
1. 依赖第三方库

2. 不能代理final类/方法
适用场景目标类已实现接口,且无性能极致要求目标类未实现接口,或需更高性能

通过上述对比可知,JDK动态代理适用于轻量级、接口导向的场景,而CGLIB更适合无接口类或高性能需求的场景。在Spring AOP中,默认会根据目标类是否实现接口自动选择代理方式:若实现接口则使用JDK代理,否则使用CGLIB代理(需引入CGLIB依赖)。

2.2Spring AOP源码剖析(了解即可)

Spring AOP的实现核心在于动态代理技术切面编织机制的结合,其底层依赖AnnotationAwareAspectJAutoProxyCreatorAbstractAutoProxyCreator两个核心类完成代理对象的创建。以下从核心类职责、代理生成流程两个维度进行简化剖析,聚焦关键逻辑而非完整实现细节。

2.2.1核心类体系:从注解处理到代理创建

Spring AOP的代理创建逻辑通过父子类协同实现,核心继承关系如下:

AbstractAutoProxyCreator 
└── AbstractAdvisorAutoProxyCreator└── AnnotationAwareAspectJAutoProxyCreator

1. AnnotationAwareAspectJAutoProxyCreator:注解驱动的切面处理器

作为Spring AOP的入口类,其核心职责是:

  • 扫描@Aspect注解:在Spring容器启动时,自动检测标注@Aspect的Bean,解析其中的@Before @After等通知注解;
  • 生成Advisor:将切面中的通知(Advice)与切入点(Pointcut)封装为Advisor对象(切面的最小执行单元);
  • 传递给父类处理:将生成的Advisor列表交给父类AbstractAutoProxyCreator,触发代理对象创建流程。

关键作用:作为"注解解析器",架起了@Aspect注解与底层代理逻辑的桥梁,使开发者无需手动配置Advisor。

2. AbstractAutoProxyCreator:代理创建的核心实现

作为直接父类,该类封装了代理对象创建的通用逻辑,核心方法包括:

  • postProcessAfterInitialization(Object bean, String beanName):在Bean初始化后触发代理创建,是AOP介入Bean生命周期的关键扩展点;
  • wrapIfNecessary(Object bean, String beanName, Object cacheKey):判断Bean是否需要被代理(是否匹配任何Advisor的切入点),若需要则调用createProxy()生成代理对象;
  • createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource):整合Advisor、选择代理工厂(JDK/CGLIB)、最终生成代理对象。

2.2.2代理创建的核心流程(简化版)

Spring AOP生成代理对象的过程可概括为四个关键步骤,以下结合AbstractAutoProxyCreator的核心代码逻辑展开说明:

步骤1:Bean初始化后介入(触发点)

当Bean完成初始化(如afterPropertiesSet()执行完毕),Spring容器会回调postProcessAfterInitialization方法:

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);// 判断是否需要跳过代理(如AOP基础设施类本身)if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 核心逻辑:判断是否需要创建代理并执行return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

步骤2:判断是否需要代理(切入点匹配)

wrapIfNecessary方法通过getAdvicesAndAdvisorsForBean检查当前Bean是否匹配任何Advisor的切入点:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 1. 如果已处理过或无需代理(如Advice/Pointcut类型Bean),直接返回原Beanif (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 2. 检查是否需要跳过(如AOP基础设施类)if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 3. 核心:获取匹配当前Bean的Advisor列表Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) { // 存在匹配的Advisor,需要代理this.advisedBeans.put(cacheKey, Boolean.TRUE);// 4. 创建代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

步骤3:创建代理对象(选择JDK/CGLIB)

createProxy方法通过ProxyFactory整合Advisor并选择代理方式:

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {// 1. 创建ProxyFactory并配置参数(目标类、Advisor、是否暴露代理等)ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);// 2. 配置代理目标类if (!proxyFactory.isProxyTargetClass()) {// 根据目标类是否实现接口决定是否使用CGLIBif (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);} else {// 注册目标类实现的接口evaluateProxyInterfaces(beanClass, proxyFactory);}}// 3. 添加Advisor(通知+切入点)Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);// 4. 生成代理对象return proxyFactory.getProxy(getProxyClassLoader());
}

步骤4:执行增强逻辑(拦截器链调用)

最终生成的代理对象(JDK或CGLIB)会在目标方法调用时触发拦截器链执行,顺序为:

前置通知(@Before)→ 目标方法 → 后置通知(@After)→ 返回通知(@AfterReturning)/异常通知(@AfterThrowing)

2.2.3核心原理总结

Spring AOP的实现可简化为"注解解析→切入点匹配→代理生成→方法拦截"的四步流程:

  1. AnnotationAwareAspectJAutoProxyCreator负责从@Aspect注解中提取切面逻辑,转化为可执行的Advisor
  2. AbstractAutoProxyCreator在Bean初始化后介入,通过wrapIfNecessary判断是否需要代理;
  3. 根据目标类类型选择JDK或CGLIB代理,通过ProxyFactory生成代理对象;
  4. 代理对象调用目标方法时,按顺序执行Advisor中的通知逻辑,实现横切关注点的编织。

这一设计的核心价值在于无侵入性:业务代码仅需标注注解即可获得AOP增强,无需与框架API耦合,完美体现了"面向切面编程"的设计思想。

3.小结

从框架设计视角审视,Spring AOP 的实现本质是复杂问题分层化解的典范:底层依赖动态代理技术解决代码增强的灵活性问题,中层通过面向切面编程思想实现横切逻辑的模块化封装,上层则提供简洁的声明式 API 降低开发者使用门槛。这种"技术底座-架构抽象-应用接口"的三层设计,既保证了底层技术的可扩展性(如支持 JDK/CGLIB 两种代理模式切换),又通过封装复杂度提升了开发效率。

如果文章对你有帮助的话,不要忘了点赞关注,谢谢支持喔~

http://www.dtcms.com/a/342528.html

相关文章:

  • CTFshow系列——命令执行web41-44
  • YOLOv8 原理与跨领域应用全景分析
  • CVPR | 2025 | MAP:通过掩码自回归预训练释放混合 Mamba - Transformer 视觉骨干网络的潜力
  • 【C++】仿函数和回调函数
  • Python数值取整完全指南:从基础到金融工程实践
  • uniapp实现分页,效果如图
  • 自然语言处理——04 注意力机制
  • npm全局安装后,cmd命令行可以访问,vscode访问报错
  • HTTP 403 错误:后端权限校验机制深度解析
  • 长尾关键词优化SEO核心策略
  • JeeSite 快速开发平台:全能企业级快速开发解决方案
  • 自己动手,在Mac开发机上利用ollama部署一款轻量级的大模型Phi-3:mini
  • ElasticSearch——常用命令
  • VSCode Import Cost:5 分钟学会依赖瘦身
  • java16学习笔记
  • uniapp 全局弹窗
  • 力扣1005:k次取反后最大化的数组和
  • pycharm编译器如何快速掌握一个新模块的使用方法
  • K-means 聚类算法学习
  • matplotlib 6 - Gallery Images
  • 在 Linux 中全局搜索 Word 文档内容的完整指南
  • 从零搭建Kubernetes集群:常见踩坑与解决方案
  • Django中的MVC和MVT模式
  • Unity接入DeepSeek实现AI对话功能
  • 解析火语言 RPA 核心功能:让流程自动化更高效​
  • leetcode 76 最小覆盖子串
  • 有关spring-ai的defaultSystem与systemMessage优先级
  • AI 发展的伦理困局:在创新与规范间寻找平衡
  • MYSQL库及表的操作
  • Linux进程间传递文件描述符:为什么不能用FIFO而要用Unix域套接字?