【重写SpringFramework】第二章aop模块:AOP代理下(chapter 2-5)

本文首发微信公众号【Java编程探微】


1. 前言

上一节介绍了 AOP 代理的基本情况,并实现了通过 JDK 动态代理的方式来创建代理对象。这种方式的特点是必须定义接口,就代码层面来说有些繁琐。Spring 还提供了另一种创建代理的方式,Cglib 框架是通过自动生成子类的方式完成代理的,省去了定义接口的环节。

但是,AOP 代理并不是十全十美的,在某些情况下会导致 AOP 失效的情况。为了搞清楚这一问题,我们有必要探讨一下 AOP 代理的本质。只有从原理上有所认识,才能解释各种现象是如何发生的。

2. Cglib 代理

Spring 核心包集成了 CGLIB 相关的 API,其中 Enhancer 类负责创建代理对象,MethodInterceptor 接口(这是 CGLIB 相关的接口,不是 aopalliance 包的同名接口)负责处理拦截的逻辑。Enhancer 作为门面类至少需要完成三个步骤,如下所示:

  1. 设置父类的 Class 属性,实际上就是目标类,因为生成的代理是其子类。
  2. 设置 Callback,通过回调的方式执行增强逻辑。
  3. 调用 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 方法可以分为三步:

  1. 获取目标类的接口,并添加到 AdvisedSupport 实例中。如果类名中包含 $$,说明对象是通过 Cglib 方式创建的,需要获取父类,也就是原始对象。
  2. 创建 Enhancer 实例,设置相关属性。
  3. 调用 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 作为回调,在调用目标方法时,自动执行 DynamicAdvisedInterceptorintercept 方法。该方法与 JdkDynamicAopProxyinvoke 方法的调用逻辑几乎完全一致,简单回顾一下执行过程,同样分为三步:

  1. 准备工作。如果 exposeProxy 属性为 true,则将代理对象绑定到当前线程上
  2. 获取能够应用于目标方法的拦截器链
  3. 如果拦截器链不为空,说明是增强方法,创建 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 集合,然后过滤出适用于某个方法的拦截器链。这中间经过了由 AdvisorMethodInterceptor 的转变,这说明过滤的过程就是切面特性的体现,即哪些方法应该被增强,哪些不应该被增强。因此,我们需要对 Advisor 集合和拦截器链的概念有明确的认识,主要有两点区别:

  • Advisor 集合是面向类的,拦截器链是面向某个方法的,拦截器链是 Advisor 集合的子集。
  • Advisor 集合的元素类型是 Advisor,具有切面的语义。拦截器链的元素类型为 Object,实际上是 MethodInterceptor,只负责增强的逻辑。

4.2 示例代码

假如存在两个增强组件,TransactionAdvisor 的作用是开启数据库事务,支持解析 @Transactional 注解。LoggerAdvisor 的作用是打印日志,支持解析 @Logger 注解。然后定义一个类 Target,其中 foo 方法声明了两个注解,bar 方法只声明了一个注解。在创建代理时,代理对象持有 TransactionAdvisorLoggerAdvisor 两个增强组件。

//示例代码
public class Target {

    @Transactional
    @Logger
    public void foo() {}

    @Logger
    public void bar() {}
}

代理对象创建之后,调用 foobar 方法时会执行相应的增强逻辑。结合下图,分别描述两个方法的调用情况:

  • 调用 foo 方法时,拦截器链由 TransactionInterceptorLoggerInterceptor 组成,将会执行数据库事务和打印日志两个增强逻辑。
  • 调用 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 代理回调的过程中,将代理对象绑定到当前线程上。我们已经在 JdkDynamicAopProxyCglibAopProxy 的回调方法中详细说明过了,通过 AopContextcurrentProxy 方法可以拿到当前代理对象。因此我们只需要在增强方法中拿到代理对象,然后通过代理对象来调用其他增强方法即可。调整后的代码如下:

//示例代码:暴露代理对象
public class Target {

    public void foo() {
        //获取代理对象的引用
        Test proxy = (Test) AopContext.currentProxy();
        //通过代理对象调用bar方法,再次触发AopProxy的回调
        proxy.bar();
    }

    public void bar() {}
}

5.2 ThreadLocal 的不足

代理对象保存在 AopContextThreadLocal 类型字段中,也就是说代理对象是和当前线程绑定的。如下代码所示,执行 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 对象作为回调,然后调用 Enhancercreate 方法创建代理对象。注意,这里强转的是实现类的类型,而非接口类型。

@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
         └─ aop.test
            └─ proxy
               ├─ CglibProxy.java (+)
               ├─ ProxyTest.java (*)
               └─ SimpleCglibInterceptor.java (+)

注:+号表示新增、*表示修改
  • 项目地址:https://gitee.com/stimd/spring-wheel

  • 本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter2-5

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值