基于代理(Proxy)的AOP实现

首先,这是一种基于代理(Proxy)的实现方式。下面这张图很好地表达了这层关系:

这张图反映了参与到AOP过程中的几个关键组件(以@Before Advice为例):

  1. 调用者Beans - 即调用发起者,它只知道目标方法所在Bean,并不清楚代理以及Advice的存在
  2. 目标方法所在Bean - 被调用的目标方法
  3. 生成的代理 - 由Spring AOP为目标方法所在Bean生成的一个代理对象
  4. Advice - 切面的执行逻辑

它们之间的调用先后次序反映在上图的序号中:

  1. 调用者Bean尝试调用目标方法,但是被生成的代理截了胡
  2. 代理根据Advice的种类(本例中是@Before Advice),对Advice首先进行调用
  3. 代理调用目标方法
  4. 返回调用结果给调用者Bean(由代理返回,没有体现在图中)

为了理解清楚这张图的意思和代理在中间扮演的角色,不妨看看下面的代码:

@Componentpublic class SampleBean { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); }}@Aspect@Componentpublic class SampleAspect { @Before("execution(void advicedMethod())") public void logException() { System.out.println("Aspect被调用了"); }}sampleBean.invokeAdvicedMethod(); // 会打印出 "Aspect被调用了" 吗?

SampleBean扮演的就是目标方法所在Bean的角色,而SampleAspect扮演的则是Advice的角色。很显然,被AOP修饰过的方法是advicedMethod(),而非invokeAdvicedMethod()。然而,invokeAdvicedMethod()方法在内部调用了advicedMethod()。那么会打印出来Advice中的输出吗?

答案是不会

如果想不通为什么会这样,不妨再去仔细看看上面的示意图。

这是在使用Spring AOP的时候可能会遇到的一个问题。类似这种间接调用不会触发Advice的原因在于调用发生在目标方法所在Bean的内部,和外面的代理对象可是没有半毛钱的关系哦。我们可以把这个代理想象成一个中介,只有它知道Advice的存在,调用者Bean和目标方法所在Bean知道彼此的存在,但是对于代理或者是Advice却是一无所知的。因此,没有通过代理的调用是绝无可能触发Advice的逻辑的。如下图所示:

Spring AOP的两种实现方式

Spring AOP有两种实现方式:

  • 基于接口的动态代理(Dynamic Proxy)
  • 基于子类化的CGLIB代理

我们在使用Spring AOP的时候,一般是不需要选择具体的实现方式的。Spring AOP能根据上下文环境帮助我们选择一种合适的。那么是不是每次都能够这么”智能”地选择出来呢?也不尽然,下面的例子就反映了这个问题:

@Componentpublic class SampleBean implements SampleInterface { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); }}public interface SampleInterface {}

在上述代码中,我们为原来的Bean实现了一个新的接口SampleInterface,这个接口中并没有定义任何方法。这个时候,再次运行相关测试代码的时候就会出现异常(摘录了部分异常信息):

org.springframework.beans.factory.BeanCreationException: Error ceating bean with name 'com.destiny1020.SampleBeanTest': Injection of autowired dependencies failedCaused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.destiny1020.SampleBean] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. 

也就是说在Test类中对于Bean的Autowiring失败了,原因是创建SampleBeanTest Bean的时候发生了异常。那么为什么会出现创建Bean的异常呢?从异常信息来看并不明显,实际上这个问题的根源在于Spring AOP在创建代理的时候出现了问题。

这个问题的根源可以在这里得到一些线索:

Spring AOP Reference - AOP Proxies

文档中是这样描述的(每段后加上了翻译):

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.Spring AOP默认使用标准的JDK动态代理来实现AOP代理。这能使任何借口(或者一组接口)被代理。Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.Spring AOP也使用CGLIB代理。对于代理classes而非接口这是必要的。如果一个业务对象没有实现任何接口,那么默认会使用CGLIB。由于面向接口而非面向classes编程是一个良好的实践;业务对象通常都会实现一个或者多个业务接口。强制使用CGLIB也是可能的(希望这种情况很少),此时你需要advise的方法没有被定义在接口中,或者你需要向方法中传入一个具体的对象作为代理对象。

因此,上面异常的原因在于:

强制使用CGLIB也是可能的(希望这种情况很少),此时你需要advise的方法没有被定义在接口中。

我们需要advise的方法是SampleBean中的advicedMethod方法。而在添加接口后,这个方法并没有被定义在该接口中。所以正如文档所言,我们需要强制使用CGLIB来避免这个问题。

强制使用CGLIB很简单:

@Configuration@EnableAspectJAutoProxy(proxyTargetClass = true)@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}

向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true即可。

CGLIB实现AOP代理的原理是通过动态地创建一个目标Bean的子类来实现的,该子类的实例就是AOP代理,它建立起了目标Bean到Advice的联系。

当然还有另外一种解决方案,那就是将方法定义声明在新创建的接口中并且去掉之前添加的proxyTargetClass = true:

@Componentpublic class SampleBean implements SampleInterface { @Override public void advicedMethod() { } @Override public void invokeAdvicedMethod() { advicedMethod(); }}public interface SampleInterface { void invokeAdvicedMethod(); void advicedMethod();}@Configuration@EnableAspectJAutoProxy@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}
  • 从Debug Stacktrace的角度也可以看出这两种AOP实现方式上的区别:
  • JDK动态代理
  • CGLIB
  • 关于动态代理和CGLIB这两种方式的简要总结如下:
  • JDK动态代理(Dynamic Proxy)
  • 基于标准JDK的动态代理功能
  • 只针对实现了接口的业务对象
  • CGLIB
  • 通过动态地对目标对象进行子类化来实现AOP代理,上面截图中的SampleBean$$EnhancerByCGLIB$$1767dd4b即为动态创建的一个子类
  • 需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)来强制使用
  • 当业务对象没有实现任何接口的时候默认会选择CGLIB

springaop实现原理_spring AOP的实现原理相关推荐

  1. spring mvc原理_Spring MVC的工作原理,我们来看看其源码实现

    来源:https://www.cnblogs.com/youzhibing/p/10695012.html 作者:youzhibing2904 遗留问题 在关于利用maven搭建ssm的博客,我们一起 ...

  2. Spring的AOP与IOC原理

    一.IOC(Inversion of Control):控制反转 传统模式下使用类的方法与属性,我们需要new出这个类的对象,然后使用对象进行方法调用,这种方法耦合度极高,为了降低耦合度,Spring ...

  3. Spring 容器AOP的实现原理——动态代理

    本文来自极客学院 Spring 容器AOP的实现原理--动态代理 之前写了一篇关于IOC的博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大 ...

  4. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架...

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  5. 底层原理_Spring框架底层原理IoC

    一.概述 Spring是一个轻量级的开源JavaEE框架 Spring可以解决企业应用开发的复杂性 Spring两大核心部分:IoC和AOP 特点: 方便解耦,简化开发 AOP编程支持 方便程序测试 ...

  6. spring mvc原理_Spring常见问题整理

    一.为什么要使用 spring? 1.Spring简介 spring 是一个开源的轻量级 JavaBean 容器框架.使用 JavaBean 代替 EJB ,并提供了丰富的企业应用功能,降低应用开发的 ...

  7. aop实现原理_从宏观的实现原理和设计本质入手,带你理解 AOP 框架的原理

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:FeelsChaotic juejin.im/post/5c57b2d5e51d45 ...

  8. java 事务实现原理_Spring中事务用法示例及实现原理详解

    前言 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现. 关于事务,简单来说,就是为了保证数据 ...

  9. 从宏观的实现原理和设计本质入手,带你理解 AOP 框架的原理

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:FeelsChaotic juejin.im/post/5c57b2d5e51d45 ...

最新文章

  1. Windows 7 的系统优化方案
  2. 5.5 SVM补充-机器学习笔记-斯坦福吴恩达教授
  3. WSS 扩展文件夹的属性--如何给文件夹添加扩展字段
  4. 使用Vitamio打造自己的Android万能播放器(7)——在线播放(下载视频)
  5. 《计算机科学概论》—第3章3.3节文本表示法
  6. Qt中全局变量的使用
  7. 4012最长的最短路径的求解(C++,迪杰斯特拉算法,注释全,附迪杰斯特拉算法详解文章)
  8. Laravel核心解读--Facades
  9. 录像回放丨2020数据技术嘉年华现场视频现已上线
  10. Zabbix通过Smokeping检测网络质量并告警
  11. 从零基础入门Tensorflow2.0 ----六、32cifar10数据训练
  12. PowerShell 远程连接与其它技巧
  13. 重置mysql数据库密码_重置mysql数据库密码的方法
  14. English语法_分词用法-作名词
  15. Vue安装必要插件element-ui插件及axios依赖(详细)
  16. 【愚公系列】2022年10月 微信小程序-电商项目-商品详情页面的标题及价格功能实现
  17. 谷歌学术高级搜索技巧
  18. 机器人唱歌bgm_Soul app里面机器人匹配的那首bgm是什么呀?好好听!!!求玩过soul的大神告知!!...
  19. 【DG】[三思笔记]一步一步学DataGuard
  20. Excel学习日记:L31-布尔逻辑/and和or函数/sumproduct函数

热门文章

  1. Atomic原子类常用方法总结(包含四大类型)
  2. zabbix 安装_Zabbix的WEB安装与配置
  3. 基于RT-Thread实现的小游戏(贪吃蛇、俄罗斯方块)
  4. 数据结构与算法 / 哈希算法
  5. 服务器远程显示用户忙,服务器远程显示用户忙
  6. matlab cameraman,cameraman.tif 原图
  7. 双向TVS管 30KP42CA
  8. 32位微型计算机原理...,32位微型计算机原理·接口技术及其应用
  9. java限制数字_是否存在将我的通用方法限制为数字类型的约束?
  10. misc高阶 攻防世界_玄幻世界(修真、仙侠、奇幻、神话)修炼体系基础模型设定。...