一、基于代理的AOP实现
首先,Spring AOP是一种基于代理(Proxy)的实现,下面这张图很好的表达了这层关系:
这张图反映了参与到AOP过程中的几个关键组件(以@Before Advice为例):
- 调用者Beans:即调用的发起者,它只知道目标方法所在Bean,并不清楚代理以及Advice的存在
- 目标方法所在Bean:被调用的方法
- 生成的代理:由Spring AOP为目标方法所在Bean生成的一个代理对象
- Advice:切面的执行逻辑
它们之间的调用先后次序反映在上图的序号中:
- 调用者Bean尝试调用目标方法,但是被生成的代理截了胡
- 代理根据Advice的种类(本例是@Before Advice),对Advice首先进行调用
- 代理调用目标方法
- 返回调用结果给调用者Bean(由代理返回,没有体现在图中)
为了理解清楚这张图的意思和代理在中间扮演的角色,不妨看看下面的代码:
@Component
public class SampleBean{
public void adviceMethod(){
...
}
public void invokeAdviceMethod(){
adviceMethod();
}
}
@Aspect
@Component
public class SampleAspect{
@Before("execution(void adviceMethod()");
public void logException(){
System.out.println("Aspect被调用了");
}
}
sampleBean.invokeAdviceMethod(); //会打印出"Aspect被调用了吗?"
SampleBean扮演的就是目标方法所在Bean的角色,而SampleAspect扮演的则是Advice的角色。很显然,被AOP修饰过的方法是adviceMethod(),而非invokeAdviceMethod()。然而,invokeAdviceMethod()方法在内部调用了adviceMethod()。那么会打印出Advice的输出吗?
答案是不会
如果想不通为什么会这样,不妨再去仔细看看上面的示意图。
这是在使用Spring AOP的时候可能会遇到的一个问题。类似这种间接调用不会触发Advice的原因在于调用发生在目标方法所在Bean的内部,和外面的代理对象可是没有半毛钱关系哦。我们可以把这个代理想象成一个中介,只有它知道Advice的存在,调用者Bean和目标方法所在Bean知道彼此的存在,但是对于代理或者是Advice却是一无所知的。因此,没有通过代理的调用时绝无可能触发Advice的逻辑的。如下图所示:
二、Spring AOP的两种实现方式
Spring AOP有两种实现方式:
- 基于接口的动态代理(Dynamic Proxy)
- 基于子类化的CGLIB代理
基于动态代理和CGLIB这两种方式的简要总结如下:
- JDK动态代理(Dynamic Proxy)
- 基于标准JDK的动态代理功能
- 只针对实现了接口的业务对象
- CGLIB
- 通过动态地对目标对象进行子类化来实现AOP代理
- 需要指定@EnableAspectJAutoProxy(proxyTargetClass=true)来强制使用
- 当业务对象没有实现任何接口的时候默认会选择CGLIB