Spring源码学习(2) —— 不同切面的执行顺序分析

上篇讲到Spring中切面代理对象和切面链的生成逻辑,本篇接上一篇文章继续分析Spring是如何沿着切面链持续推进的。

1. 基本概念

1)连接点(JointPoint)
程序执行时的某个特定位置,由两个因素确定:程序执行点和方位。
2)切点(Pointcut)
描述了连接点的位置,借助切点可以定位到具体的程序执行点。
3)增强(Advice)
织入目标类连接点上的一段代码,同时包含了连接点的方位。结合切点和增强,可以确定连接点并实施增强逻辑。
4)目标对象(Target)
增强逻辑织入的目标类。
5)引介(Introduction)
一种特殊的增强,通过引入第三方接口,可以为目标类添加一些属性和方法。
6)织入(Weaving)
将增强添加到具体连接点的过程。
7)切面(Aspect)
由切点和增强构成,因此它既包含横切逻辑的定义,又包含连接点的定义。在代码层面,与之对应的对象为Advisor。

2. 增强类型
Spring源码学习(2) —— 不同切面的执行顺序分析_第1张图片
增强的类体系结构
3. 切面类型

在Spring中,切面可以分为3类:
1)一般切面(Advisor):仅包含一个Advice,连接点是目标类的所有方法。
2)具有切点的切面(PointcutAdvisor):同时包含Advice和Pointcut,可以灵活定义连接点。
3)引介切面(IntroductionAdvisor):引介增强对应的切面,应用于类层面,切点使用ClassFilter来定义。

4. 源码解读

构建切面链的代码如下:

public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {

    @Override
    public List getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, Class targetClass) {

        // This is somewhat tricky... We have to process introductions first,
        // but we need to preserve order in the ultimate list.
        // 这里有点棘手,我们需要优先处理引介增强,但又需要保持最终的顺序正确
        List interceptorList = new ArrayList(config.getAdvisors().length);
        Class actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
        // 是否配置了引介增强 
        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
        // 注册增强的适配器,该适配器用来将增强转换成对应的拦截器
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

        for (Advisor advisor : config.getAdvisors()) {
            // 具有切点的切面
            if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                // 对类进行静态切点检查
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    // 对增强进行适配,转换成对应的拦截器
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
                        // 是否需要进行动态匹配
                        if (mm.isRuntime()) {
                            // Creating a new object instance in the getInterceptors() method
                            // isn't a problem as we normally cache created chains.
                            for (MethodInterceptor interceptor : interceptors) {
                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                            }
                        }
                        else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
            }
            // 引介切面
            else if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                    Interceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            }
            // 一般切面
            else {
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }

        return interceptorList;
    }

    /**
     * Determine whether the Advisors contain matching introductions.
     */
    private static boolean hasMatchingIntroductions(Advised config, Class actualClass) {
        for (int i = 0; i < config.getAdvisors().length; i++) {
            Advisor advisor = config.getAdvisors()[i];
            if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (ia.getClassFilter().matches(actualClass)) {
                    return true;
                }
            }
        }
        return false;
    }

}
 
 

可以看到,这里正是按照切面的3种类型分别进行处理的。
对于具有切点的切面,需要按照ClassFilter和MethodMatcher进行匹配,同时,如果是动态切面,需要先转换成InterceptorAndDynamicMethodMatcher对象,再加入切面链。
对于引介切面,只需要按照ClassFilter进行匹配即可。
而对于一般切面,则是直接加入切面链。

回到JdkDynamicAopProxy#invoke()方法,我们来看下,切面链组装完成后,是如何沿着切面链持续推进的。

    // Get the interception chain for this method.
    List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
    // Check whether we have any advice. If we don't, we can fallback on direct
    // reflective invocation of the target, and avoid creating a MethodInvocation.
    if (chain.isEmpty()) {
        // We can skip creating a MethodInvocation: just invoke the target directly
        // Note that the final invoker must be an InvokerInterceptor so we know it does
        // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
    }
    else {
        // We need to create a method invocation...
        invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        // Proceed to the joinpoint through the interceptor chain.
        retVal = invocation.proceed();
    }
 
 

ReflectiveMethodInvocation#proceed():

    @Override
    public Object proceed() throws Throwable {
        // 沿着切面链顺序推进,切面链调用完毕,则开始执行目标方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // 如果是动态切面,需要进行参数匹配,以确定是否需要执行该动态横切逻辑
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // 动态切面匹配失败,调过,执行下一个切面
                return proceed();
            }
        }
        else {
            // 非动态切面,无需再次匹配,直接执行
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

    protected Object invokeJoinpoint() throws Throwable {
        return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
    }

好,看到这里,问题来了:
1)当一个执行点匹配了多个增强时,不同增强之间是按照什么顺序来执行的?
2)从代码上看,所有增强都执行完毕后才会执行目标方法,那后置增强又是如何实施的?
带着这两个疑问,我们继续往下看。

3. 不同增强执行顺序分析

目标类:

public class Target implements TargetInterface{

    public void exeTarget() {
        System.out.println("execute target.");
    }
}

前置增强:

public class BeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("before advice.");
    }
}

后置增强:

public class AfterReturnAdvice implements AfterReturningAdvice {

    public void afterReturning(Object o, Method method, Object[] objects, Object o1)
            throws Throwable {
        System.out.println("after returning advice.");
    }
}

配置文件:



    
    
    
    

    
    

4.1 前置增强+后置增强

先来看最简单的例子,执行点同时匹配一个前置增强和一个后置增强,即interceptorNames配置为:

p:interceptorNames="beforeAdvice,afterReturningAdvice"

此时输出如下,这没有任何问题:

before advice.
execute target.
after returning advice.

调增配置文件为

p:interceptorNames="afterReturningAdvice, beforeAdvice

此时输出没有任何变化,这是符合横切逻辑的,但是从刚才的代码逻辑来看,在切面链中,后置增强应该先得到执行,这中间发生了神马?我们继续往下看。
前置拦截器:

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {

    private MethodBeforeAdvice advice;


    /**
     * Create a new MethodBeforeAdviceInterceptor for the given advice.
     * @param advice the MethodBeforeAdvice to wrap
     */
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        return mi.proceed();
    }
}

后置拦截器:

public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {

    private final AfterReturningAdvice advice;


    /**
     * Create a new AfterReturningAdviceInterceptor for the given advice.
     * @param advice the AfterReturningAdvice to wrap
     */
    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
}

从这里可以看到,整个推进步骤如下:

// 开始处理切面链
1)ReflectiveMethodInvocation#proceed(); 
// 开始处理后置增强,没有实施增强逻辑
2)AfterReturningAdviceInterceptor#invoke(); 
// 回调proceed()方法 
3)ReflectiveMethodInvocation#proceed();
// 开始处理前置增强
4)MethodBeforeAdviceInterceptor#invoke();
// 实施前置增强逻辑
5)MethodBeforeAdvice#before();
// 回调proceed()方法
6)ReflectiveMethodInvocation#proceed();
// 切面链处理完毕,调用目标方法
7)ReflectiveMethodInvocation#invokeJoinpoint();
// 实施后置增强逻辑
8)AfterReturningAdvice#afterReturning();

可以看到,通过在拦截器中回调proceed()方法,保证了不论配置文件中p:interceptorNames属性的顺序如何,后置增强永远在前置增强之后实施。

4.2 前置增强+后置增强+环绕增强

现在,我们在上述例子的基础上再添加一个环绕增强,看看增强又是按照什么顺序被实施的。
环绕增强:

public class AroundAdvice implements MethodInterceptor {

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object obj = null;
        System.out.println("before around advice.");
        obj = methodInvocation.proceed();
        System.out.println("after around advice.");
        return obj;
    }
}

可以看到,与前置、后置增强不一样,环绕增强拥有了目标方法的调用权,需要在环绕增强中主动调用MethodInvocation#proceed()方法。
interceptorNames配置为:

p:interceptorNames="afterReturningAdvice, beforeAdvice, aroundAdvice"

按照前面的分析,此时切面链的推进顺序应该是这样的:

// 开始处理切面链
1)ReflectiveMethodInvocation#proceed(); 
// 开始处理后置增强,没有实施增强逻辑,执行点1入栈
2)AfterReturningAdviceInterceptor#invoke(); 
// 回调proceed()方法 
3)ReflectiveMethodInvocation#proceed();
// 开始处理前置增强
4)MethodBeforeAdviceInterceptor#invoke();
// 实施前置增强逻辑
5)MethodBeforeAdvice#before();
// 回调proceed()方法
6)ReflectiveMethodInvocation#proceed();
// 开始处理环绕增强,执行点2入栈
7)AroundAdvice#invoke();
// 回调proceed()方法
8)ReflectiveMethodInvocation#proceed();
// 切面链处理完毕,调用目标方法
9)ReflectiveMethodInvocation#invokeJoinpoint();
// 继续处理环绕增强,执行点2出栈
10)AroundAdvice#invoke();
// 实施后置增强逻辑,执行点1出栈
11)AfterReturningAdvice#afterReturning();

可以看到,由于在实施后置增强之前都会先回调proceed()方法,因此后置增强的执行点被依次保存在了栈中,程序最后的输出为:

before advice.
before around advice.
execute target.
after around advice.
after returning advice.

如果在环绕增强中不回调proceed()方法,同时调整aroundAdvice在配置文件中的顺序,结果会是怎样的呢?有兴趣的朋友可以自己动手试一试_

4.3 前置增强+后置增强+环绕增强+引介增强

在上述例子的基础上继续增加一个引介增强,看看又会发生什么:
引介增强:

public interface Introduction {
    void setIntroductionAdvice();
}
public class IntroductionAdvice extends DelegatingIntroductionInterceptor implements Introduction{

    public void setIntroductionAdvice() {
        System.out.println("set introduction advice.");
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object obj = null;
        System.out.println("introduction advice begin.");
        obj = super.invoke(methodInvocation);
        System.out.println("introduction advice end.");
        return obj;
    }
}

此时配置文件也修改为:

......
p:interfaces="com.youzan.shys.advice.seq.Introduction"
p:target-ref="target"
p:proxyTargetClass="true"
p:interceptorNames="afterReturningAdvice, beforeAdvice, aroundAdvice, introductionAdvice"
......

测试类修改为:

public class AdviceSeqTest {

    public static void main(String[] args) {
        String configPath = "advice/seq/beans.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
        TargetInterface target = (TargetInterface) context.getBean("adviceSeq");

        Introduction introduction = (Introduction)target;
        introduction.setIntroductionAdvice();
    }
}

在观察运行结果之前,我们先看一下引介增强的实现逻辑:

DelegatingIntroductionInterceptor#invoke():

    public Object invoke(MethodInvocation mi) throws Throwable {
        if (isMethodOnIntroducedInterface(mi)) {
            // Using the following method rather than direct reflection, we
            // get correct handling of InvocationTargetException
            // if the introduced method throws an exception.
            Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());

            // Massage return value if possible: if the delegate returned itself,
            // we really want to return the proxy.
            if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
                Object proxy = ((ProxyMethodInvocation) mi).getProxy();
                if (mi.getMethod().getReturnType().isInstance(proxy)) {
                    retVal = proxy;
                }
            }
            return retVal;
        }

        return doProceed(mi);
    }

    protected final boolean isMethodOnIntroducedInterface(MethodInvocation mi) {
        Boolean rememberedResult = this.rememberedMethods.get(mi.getMethod());
        if (rememberedResult != null) {
            return rememberedResult;
        }
        else {
            // Work it out and cache it.
            boolean result = implementsInterface(mi.getMethod().getDeclaringClass());
            this.rememberedMethods.put(mi.getMethod(), result);
            return result;
        }
    }

可以看到,引介增强的实现逻辑与环绕增强非常类似,他们都拥有了目标方法的调用权,只不过调用目标方法的方式有所区别。引介增强在调用目标方法之前会先判断该方法是否是引介接口的方法,是的话才会通过反射调用目标方法,否则调过,与环绕增强一样,调用proceed()方法。
按照前面的分析,此时的输出就很好理解了:

before advice.
before around advice.
introduction advice begin.
set introduction advice.
introduction advice end.
after around advice.
after returning advice.

更多技术文章,咱们公众号见,我在公众号里等你~

Spring源码学习(2) —— 不同切面的执行顺序分析_第2张图片
image.png

你可能感兴趣的:(Spring源码学习(2) —— 不同切面的执行顺序分析)