Spring AOP源码分析(三)拦截器链的执行过程

Posted by YiBo on November 12, 2020

1. 前记

在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程

当目标方法被多个通知匹配到时,Spring 通过引入拦截器链来保证每个通知的正常执行

在本文中,我们将会通过源码了解到 Spring 是如何支持 expose-proxy 属性的,以及通知与拦截器之间的关系,拦截器链的执行过程等

2. 背景知识

expose-proxy 怎么用

举个例子

1
2
3
4
5
6
7
8
9
10
11
<bean id="hello" class="xyz.coolblog.aop.Hello"/>
<bean id="aopCode" class="xyz.coolblog.aop.AopCode"/>

<aop:aspectj-autoproxy expose-proxy="true" />

<aop:config expose-proxy="true"> // 在这里
    <aop:aspect id="myaspect" ref="aopCode">
        <aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" />
        <aop:before method="before" pointcut-ref="helloPointcut" />
    </aop:aspect>
</aop:config>
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Hello implements IHello {

    @Override
    public void hello() {
        System.out.println("hello");
        this.hello("world");
    }

    @Override
    public void hello(String hello) {
        System.out.println("hello " +  hello);
    }
}

hello()方法调用了同类中的另一个方法hello(String),此时hello(String)上的切面逻辑就无法执行了。这里,我们要对hello()方法进行改造,强制它调用代理对象中的hello(String)。改造结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Hello implements IHello {

    @Override
    public void hello() {
        System.out.println("hello");
        ((IHello) AopContext.currentProxy()).hello("world");
    }

    @Override
    public void hello(String hello) {
        System.out.println("hello " +  hello);
    }
}

如上,AopContext.currentProxy()用于获取当前的代理对象。当 expose-proxy 被配置为 true 时,该代理对象会被放入 ThreadLocal 中。关于 expose-proxy,这里先说这么多,后面分析源码时会再次提及

3. 源码分析

3.1 JDK 动态代理逻辑分析

  1. 检测 expose-proxy 是否为 true,若为 true,则暴露代理对象
  2. 获取适合当前方法的拦截器
  3. 如果拦截器链为空,则直接通过反射执行目标方法
  4. 若拦截器链不为空,则创建方法调用 ReflectiveMethodInvocation 对象
  5. 调用 ReflectiveMethodInvocation 对象的 proceed() 方法启动拦截器链
  6. 处理返回值,并返回该值

3.2 获取所有的拦截器

  1. 从缓存中获取当前方法的拦截器链
  2. 若缓存未命中,则调用 getInterceptorsAndDynamicInterceptionAdvice 获取拦截器链
  3. 遍历通知器列表
  4. 对于 PointcutAdvisor 类型的通知器,这里要调用通知器所持有的切点(Pointcut)对类和方法进行匹配,匹配成功说明应向当前方法织入通知逻辑
  5. 调用 getInterceptors 方法对非 MethodInterceptor 类型的通知进行转换
  6. 返回拦截器数组,并在随后存入缓存中

3.3 启动拦截器链

4. 总结

感觉不太理解,就不贴过来了

看到这里,感觉切面什么的和这个拦截器有了关联,但说不清在哪里,

是JDK中的invoke(CGLIB中的interceptor) 这是拦截器吧?

然后从aspect中获取到的拦截器

这个后续再看吧