【重写SpringFramework】第二章aop模块:AOP代理下(chapter 2-5)
1. 前言
上一节介绍了 AOP 代理的基本情况,并实现了通过 JDK 动态代理的方式来创建代理对象。这种方式的特点是必须定义接口,就代码层面来说有些繁琐。Spring 还提供了另一种创建代理的方式,Cglib 框架是通过自动生成子类的方式完成代理的,省去了定义接口的环节。
但是,AOP 代理并不是十全十美的,在某些情况下会导致 AOP 失效的情况。为了搞清楚这一问题,我们有必要探讨一下 AOP 代理的本质。只有从原理上有所认识,才能解释各种现象是如何发生的。
2. Cglib 代理
Spring 核心包集成了 CGLIB 相关的 API,其中 Enhancer
类负责创建代理对象,MethodInterceptor
接口(这是 CGLIB 相关的接口,不是 aopalliance
包的同名接口)负责处理拦截的逻辑。Enhancer
作为门面类至少需要完成三个步骤,如下所示:
- 设置父类的
Class
属性,实际上就是目标类,因为生成的代理是其子类。 - 设置
Callback
,通过回调的方式执行增强逻辑。 - 调用
create
方法创建代理对象,可以强转为目标对象的类型。与之对比,JDK 动态代理必须强转为接口类型。
Target target = new Target();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Target.class); //设置目标类
enhancer.setCallback(new MethodInterceptor() { //设置拦截器
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib拦截");
return methodProxy.invoke(method, args);
}
});
Target proxy = (Target) enhancer.create(); //创建代理对象
proxy.doSomething();
3. CglibAopProxy
3.1 创建代理对象
CglibAopProxy
实现了 AopProxy
接口,作用是通过 Cglib 的方式创建代理对象。同样分为两个阶段,首先是创建代理对象。getProxy
方法可以分为三步:
- 获取目标类的接口,并添加到
AdvisedSupport
实例中。如果类名中包含$$
,说明对象是通过 Cglib 方式创建的,需要获取父类,也就是原始对象。 - 创建
Enhancer
实例,设置相关属性。 - 调用
createProxyClassAndInstance
方法创建代理。
public class CglibAopProxy implements AopProxy {
protected final AdvisedSupport advised;
public CglibAopProxy(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object getProxy() {
Class<?> rootClass = this.advised.getTargetClass();
Class<?> proxySuperClass = rootClass;
//1. 获取目标类的接口,并添加到AdvisedSupport实例中
//如果目标类是通过Cglib的方式创建的,类名中包含 $$
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
//2. 创建增强器,设置相关属性
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new DefaultGeneratorStrategy());
Callback[] callbacks = getCallbacks();
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackTypes(types);
//3. 创建代理
return createProxyClassAndInstance(enhancer, callbacks);
}
}
第二步,Enhancer
最重要的属性有两个,一是 superclass
,Cglib 是通过生成子类的方式创建代理,因此设置的是目标对象的 Class
信息。二是 Callback
数组,由 getCallbacks
方法创建,具体来说就是将 AdvisedSupport
包装成一个 DynamicAdvisedInterceptor
实例,在之后的回调流程中使用。
//所属类[cn.stimd.spring.aop.framework.CglibAopProxy]
private Callback[] getCallbacks() {
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
return new Callback[] {aopInterceptor};
}
第三步,CglibAopProxy
提供的实现是常规的创建代理的方式,即通过调用构造器的方式。
//所属类[cn.stimd.spring.aop.framework.CglibAopProxy]
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
enhancer.setCallbacks(callbacks);
enhancer.setInterceptDuringConstruction(false);
return enhancer.create();
}
子类 ObjenesisCglibAopProxy
重写了 createProxyClassAndInstance
方法,SpringObjenesis
组件可以绕过构造函数来创建实例。
public class ObjenesisCglibAopProxy extends CglibAopProxy {
private static final SpringObjenesis objenesis = new SpringObjenesis();
public ObjenesisCglibAopProxy(AdvisedSupport advised) {
super(advised);
}
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = null;
if (objenesis.isWorthTrying()) {
proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
}
if (proxyInstance == null) {
try {
proxyInstance = proxyClass.newInstance();
} catch (Throwable ex) {
throw new BeansException("无法使用Objenesis来创建代理对象");
}
}
((Factory) proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}
}
3.2 调用过程
由于设置了 DynamicAdvisedInterceptor
作为回调,在调用目标方法时,自动执行 DynamicAdvisedInterceptor
的 intercept
方法。该方法与 JdkDynamicAopProxy
的 invoke
方法的调用逻辑几乎完全一致,简单回顾一下执行过程,同样分为三步:
- 准备工作。如果
exposeProxy
属性为true
,则将代理对象绑定到当前线程上 - 获取能够应用于目标方法的拦截器链
- 如果拦截器链不为空,说明是增强方法,创建
CglibMethodInvocation
对象执行拦截逻辑
//CglibAopProxy的内部类
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target;
try {
//1. 如果exposeProxy属性为true,将代理对象绑定到ThreadLocal上
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//获取目标对象和对应的类型
target = this.advised.getTargetSource().getTarget();
if (target != null) {
targetClass = target.getClass();
}
//2. 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
//3. 执行拦截操作,最终调用目标方法
//如果拦截器链为空,当作普通方法调用
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
retVal = methodProxy.invoke(target, args);
}else{
//拦截器不为空,执行增强逻辑并调用目标方法
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
return retVal;
}finally {
//解除代理对象与当前线程的绑定
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
}
CglibMethodInvocation
作为 CglibAopProxy
的内部类,同时又继承了 ReflectiveMethodInvocation
的子类,proceed
方法实际上是由父类实现的。CglibMethodInvocation
重写了调用目标方法的逻辑,可以看到,CglibMethodInvocation
是通过 MethodProxy
来调用方法的,而 JdkDynamicAopProxy
则是通过反射的方式调用方法。
//CglibAopProxy的内部类
private static class CglibMethodInvocation extends ReflectiveMethodInvocation{
private final MethodProxy methodProxy;
private final boolean publicMethod;
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.publicMethod) {
return this.methodProxy.invoke(this.target, this.arguments);
}
return super.invokeJoinpoint();
}
}
4. AOP 代理的本质
4.1 基本原理
AOP 代理实际上是个包装类,持有一个目标对象,以及一个 Advisor
集合。一个类中有 N 个方法,所有方法一共用到了 M 种增强,因此 Advisor
集合代表的是一个类所有的可用增强。当代理对象在使用过程中,尤其是第一次调用某个方法,需要从 Advisor
集合中寻找适用于该方法的拦截器链,然后缓存起来以便下次使用。因此对于目标对象的方法来说,拦截器链不为空的方法就是增强方法,否则只是普通的方法。
从创建代理到调用方法,整个流程中发生了两次过滤,首先过滤出适用于整个类的 Advisor
集合,然后过滤出适用于某个方法的拦截器链。这中间经过了由 Advisor
到 MethodInterceptor
的转变,这说明过滤的过程就是切面特性的体现,即哪些方法应该被增强,哪些不应该被增强。因此,我们需要对 Advisor
集合和拦截器链的概念有明确的认识,主要有两点区别:
Advisor
集合是面向类的,拦截器链是面向某个方法的,拦截器链是Advisor
集合的子集。Advisor
集合的元素类型是Advisor
,具有切面的语义。拦截器链的元素类型为Object
,实际上是MethodInterceptor
,只负责增强的逻辑。
4.2 示例代码
假如存在两个增强组件,TransactionAdvisor
的作用是开启数据库事务,支持解析 @Transactional
注解。LoggerAdvisor
的作用是打印日志,支持解析 @Logger
注解。然后定义一个类 Target
,其中 foo
方法声明了两个注解,bar
方法只声明了一个注解。在创建代理时,代理对象持有 TransactionAdvisor
和 LoggerAdvisor
两个增强组件。
//示例代码
public class Target {
@Transactional
@Logger
public void foo() {}
@Logger
public void bar() {}
}
代理对象创建之后,调用 foo
和 bar
方法时会执行相应的增强逻辑。结合下图,分别描述两个方法的调用情况:
- 调用
foo
方法时,拦截器链由TransactionInterceptor
和LoggerInterceptor
组成,将会执行数据库事务和打印日志两个增强逻辑。 - 调用
bar
方法时,拦截器链只有LoggerInterceptor
,意味着执行打印日志这一个增强逻辑。
5. AOP 失效的问题
5.1 对外暴露代理
当我们在一个增强方法中调用同一个类中的另一个增强方法,第二个方法的增强逻辑会失效,这就是 AOP 失效的问题。这是因为调用第一个方法时,引用指向的是代理对象。首先执行的是拦截器链,也就是增强逻辑。当进入第一个方法内部,this 引用指向的是目标对象,此时不存在拦截器链,第二个方法只是一个普通方法。
示例代码如下,调用 foo
方法时,实际是通过代理对象的引用调用的,因此会触发增强逻辑。但是在 foo
方法内部,this 引用指向的是目标对象,此时 bar
方法只是一个普通方法,不会触发增强逻辑。AOP 实质上是通过代理对象的回调来触发拦截器链,从而执行增强逻辑。因此,解决思路就是在调用 bar
方法时,确保使用的是代理对象的引用。
//示例代码:模拟AOP失效
public class Target {
public void foo() {
this.bar();
}
public void bar() {}
}
ProxyConfig
类的 exposeProxy
属性的作用是暴露代理对象,具体来说是在 AOP 代理回调的过程中,将代理对象绑定到当前线程上。我们已经在 JdkDynamicAopProxy
和 CglibAopProxy
的回调方法中详细说明过了,通过 AopContext
的 currentProxy
方法可以拿到当前代理对象。因此我们只需要在增强方法中拿到代理对象,然后通过代理对象来调用其他增强方法即可。调整后的代码如下:
//示例代码:暴露代理对象
public class Target {
public void foo() {
//获取代理对象的引用
Test proxy = (Test) AopContext.currentProxy();
//通过代理对象调用bar方法,再次触发AopProxy的回调
proxy.bar();
}
public void bar() {}
}
5.2 ThreadLocal 的不足
代理对象保存在 AopContext
的 ThreadLocal
类型字段中,也就是说代理对象是和当前线程绑定的。如下代码所示,执行 foo
方法的线程绑定了代理对象,但新线程并没有绑定任何对象,因此获取代理对象的操作会导致抛出异常。这一缺陷与 AOP 代理的底层逻辑有关,应当尽量避免这一情况的出现。
//示例代码:无法获取代理对象
public class Target {
public void foo() {
new Thread(() -> {
//在新线程中无法得到代理对象
Target proxy = (Target) AopContext.currentProxy();
Target.bar();
}).start();
}
public void bar() {}
}
6. 测试
6.1 CGLIB 代理
测试类 SimpleCglibInterceptor
实现了 MethodInterceptor
接口,重写 intercept
方法模拟拦截的功能。
//测试类
public class SimpleCglibInterceptor implements MethodInterceptor {
private Object target;
public SimpleCglibInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib 拦截...");
return methodProxy.invoke(target, args);
}
}
测试类 CglibTarget
没有实现任何接口,这是因为 Cglib 代理是生成子类的方式,以此和 JDK 动态代理区别开来。
//测试类
public class CglibTarget {
public void handle() {
System.out.println("执行handle方法");
}
}
在测试方法中,首先创建了 Enhancer
实例,指定代理对象的父类型(目标对象的子类),添加 SimpleCglibInterceptor
对象作为回调,然后调用 Enhancer
的 create
方法创建代理对象。注意,这里强转的是实现类的类型,而非接口类型。
@Test
public void testCglibProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibTarget.class);
enhancer.setCallback(new SimpleCglibInterceptor(new CglibTarget()));
CglibTarget proxy = (CglibTarget) enhancer.create();
proxy.handle();
}
从测试结果可以看到,代理对象创建成功,并在调用目标方法前进行拦截并打印日志。
Cglib 拦截...
执行handle方法
6.2 CGLIB AOP 代理
在测试方法中,需要将代理工厂的 proxyTargetClass
属性设置为 true
,开启 CGLIB 代理模式。调用 getProxy
方法返回的是普通对象的类型,不要使用接口类型来接收。
//测试类
@Test
public void testCglibAopProxy(){
ProxyFactory factory = new ProxyFactory();
factory.setTarget(new CglibTarget());
factory.setProxyTargetClass(true); //设置为Cglib代理模式
factory.addAdvice(new SimpleAfterReturningAdvice());
factory.addAdvice(new SimpleMethodBeforeAdvice());
CglibTarget proxy = (CglibTarget) factory.getProxy();
proxy.handle();
}
从测试结果可以看到,前置通知和返回通知的日志被打印出来,说明代理对象创建成功。
前置通知,方法名:handle
执行handle方法
返回通知,方法名:handle
6.3 对外暴露代理
在测试类 CglibTarget
定义两个方法,step1
方法调用了两次 step2
方法,第一次通过 this 引用目标对象来调用,第二次获取代理对象之后再调用。
//测试类
public class CglibTarget {
public void step1(){
System.out.println("执行step1...");
//直接调用,不会触发拦截
this.step2();
//通过对外暴露的代理对象调用会触发拦截
CglibTarget proxy = (CglibTarget) AopContext.currentProxy();
proxy.step2();
}
public void step2(){
System.out.println("执行step2...");
}
}
在测试方法中,首先创建 ProxyFactory
对象,设置 exposeProxy
属性为 true,允许向外暴露对象。然后调用 step1
方法,模拟 AOP 失效的情形。
//测试方法
@Test
public void testExposeAopProxy(){
ProxyFactory factory = new ProxyFactory();
factory.setTarget(new CglibTarget());
factory.setProxyTargetClass(true); //设置为Cglib代理模式
factory.setExposeProxy(true); //对外暴露代理
factory.addAdvice(new SimpleMethodBeforeAdvice());
CglibTarget proxy = (CglibTarget) factory.getProxy();
proxy.step1();
}
测试结果分为三组,首先是外部调用 step1
方法,由于 CglibTarget
是一个代理对象,因此 step1
方法得到了增强。其次,通过 this 关键字调用 step2
方法,由于此时处于目标方法内部,step2
方法被当作普通方法调用,不会触发拦截。第三,获取线程绑定的代理对象,然后调用 handle
方法就会再次增强。
前置通知,方法名:step1
执行step1...
-------------------------
执行step2...
-------------------------
前置通知,方法名:step2
执行step2...
7. 总结
本节介绍了 AOP 代理的第二种方式,即通过 Cglib 方式创建代理对象。这种方式的特点是无需实现接口,而是以生成子类的方式来实现。总的来说,AOP 代理是一个包装对象,持有一个目标对象,以及一组 Advisor
实例。这些 Advisor
组件应用于整个对象,对于具体方法来说还需要进一步过滤,得到一个拦截器链,实际上由 MethodInterceptor
组成。因此,AOP 代理的本质就是通过回调的方式执行拦截器链的增强逻辑,最终执行目标方法。
在实际应用中,经常会遇到 AOP 失效的情况,比如 @Transactional
或 @Async
注解失效。这是由 AOP 代理的特性决定的,主要包括以下两个方面:
- 如果在同一个对象的增强方法中调用另一个增强方法,第二个方法的增强效果会消失。这是因为增强逻辑必须由代理的回调来触发,而方法内部 this 指向的是目标对象。解决方法是对外暴露代理,通过
ThreadLocal
拿到代理对象。 - 对外暴露代理也有不足之处,比如在新线程中无法拿到代理对象,这是因为代理对象是和调用方法的线程绑定的。这属于底层逻辑的限制,应避免这种情况的出现。
8. 项目信息
新增修改一览,新增(4),修改(2)
aop
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.springwheel.aop
│ └─ framework
│ ├─ CglibAopProxy.java (+)
│ ├─ DefaultAopProxyFactory.java (*)
│ └─ ObjenesisCglibAopProxy.java (+)
└─ test
└─ java
└─ cn.stimd.springwheel.aop.test
└─ proxy
├─ CglibProxy.java (+)
├─ ProxyTest.java (*)
└─ SimpleCglibInterceptor.java (+)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7390341683617841186