【Spring AOP】【十】Spring AOP源码解析-讲一下ExposeInvocationInterceptor

1  前言

不知道你在调试的时候,有没有发现我们的通知器链上首个元素会给我放进来一个ExposeInvocationInterceptor类型的通知器,看下图是不是,我们在之前其实也说过一次只是一句话带过了,那我们本节看下它的进场时机和具体是做什么的,可以怎么做。

2  源码分析

2.1  进场时机

public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
    // 如果通知器列表是一个空列表,直接返回
    if (!advisors.isEmpty()) {
        boolean foundAspectJAdvice = false;
        for (Advisor advisor : advisors) {
            // 检测 每个通知器advisor是否存在类型是否正确
            if (isAspectJAdvice(advisor)) {
                foundAspectJAdvice = true;
                break;
            }
        }
        // 往头部添加了一个默认的通知器DefaultPointcutAdvisor 我猜的哈 我怀疑是多个通知器组成链表 这个充当头吧 我猜的哈 !!!这是我上次猜的哈, 我这个猜的不对哈
        if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
            advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
            return true;
        }
    }
    return false;
}

2.2  ExposeInvocationInterceptor源码

我们看了它的进场时机,那让我们看下这个类是干啥的(看源码看这个类的注释)源码如下:

/**
 * Interceptor that exposes the current {@link org.aopalliance.intercept.MethodInvocation}
 * as a thread-local object. We occasionally need to do this; for example, when a pointcut
 * (e.g. an AspectJ expression pointcut) needs to know the full invocation context.
 *
 * <p>Don't use this interceptor unless this is really necessary. Target objects should
 * not normally know about Spring AOP, as this creates a dependency on Spring API.
 * Target objects should be plain POJOs as far as possible.
 *
 * <p>If used, this interceptor will normally be the first in the interceptor chain.
 *
 * 意思就是 暴漏我们的通知器链对象MethodInvocation,保存在本地线程里
 * 当我们需要的完整的调用上下文时,就可以ExposeInvocationInterceptor.currentInvocation()获取到
 * 除非确实需要,否则不要使用此拦截器。
 * 通常不了解SpringAOP,因为这会创建对SpringAPI的依赖,目标对象应尽可能是普通POJO。
 * 如果使用,这个拦截器通常是拦截器链中的第一个。
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
@SuppressWarnings("serial")
public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable {

    /** Singleton instance of this class. */
    public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor();

    /**
     * Singleton advisor for this class. Use in preference to INSTANCE when using
     * Spring AOP, as it prevents the need to create a new Advisor to wrap the instance.
     */
    public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) {
        @Override
        public String toString() {
            return ExposeInvocationInterceptor.class.getName() +".ADVISOR";
        }
    };
    // 本地线程保存
    private static final ThreadLocal<MethodInvocation> invocation =
            new NamedThreadLocal<>("Current AOP method invocation");

    /**
     * Return the AOP Alliance MethodInvocation object associated with the current invocation.
     * @return the invocation object associated with the current invocation
     * @throws IllegalStateException if there is no AOP invocation in progress,
     * or if the ExposeInvocationInterceptor was not added to this interceptor chain
     */
    public static MethodInvocation currentInvocation() throws IllegalStateException {
        MethodInvocation mi = invocation.get();
        if (mi == null) {
            throw new IllegalStateException(
                    "No MethodInvocation found: Check that an AOP invocation is in progress and that the " +
                    "ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that " +
                    "advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor! " +
                    "In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() " +
                    "must be invoked from the same thread.");
        }
        return mi;
    }

    private ExposeInvocationInterceptor() {
    }

    @Override
    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        // 先获取到旧值 刚开始其实这里oldInvocation就是空 
        MethodInvocation oldInvocation = invocation.get();
        // 把我们的通知器链对象放进本地线程
        invocation.set(mi);
        try {
            // 继续执行通知器链
            return mi.proceed();
        }
        finally {
            // 在最后通知器链执行完毕的时候,设置回去oldInvocation 也就是空 
            invocation.set(oldInvocation);
        }
    }
    
    @Override
    public int getOrder() {
        /**
         * int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
         * 通知器的排序值 
         * 困惑么 Integer最小值+1 那我们普通的通知器默认的排序值不是Integer最小值么 
         * 它比我们的默认值大,说明他的优先级应该低啊  那岂不是在排序的时候就会放最后一个  怎么还能在第一个呢??
         * 看它实现的接口 PriorityOrdered 他有这个标识  所以它是老大
         */
        return PriorityOrdered.HIGHEST_PRECEDENCE + 1;
    }

    /**
     * Required to support serialization. Replaces with canonical instance
     * on deserialization, protecting Singleton pattern.
     * <p>Alternative to overriding the {@code equals} method.
     */
    private Object readResolve() {
        return INSTANCE;
    }

}

可以看到其实就是把我们的通知器链对象,放进了本地线程里,然后后续的通知器里就可以根据ExposeInvocationInterceptor.currentInvocation()来得到我们的通知器链对象,并且实现的是PriorityOrdered,排面拉满第一个,我们的后置通知,其实就用到了,我们看看:

public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        // 继续下一个通知器
        return mi.proceed();
    }
    finally {
        // 调用通知方法
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}
protected JoinPointMatch getJoinPointMatch() {
    // 看这里是不是用到了
    MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
    if (!(mi instanceof ProxyMethodInvocation)) {
        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
    }
    return getJoinPointMatch((ProxyMethodInvocation) mi);
}

3  小结

好了关于ExposeInvocationInterceptor我们就介绍到这里,有理解不对的地方欢迎指正哈。

posted @ 2023-02-23 07:08  酷酷-  阅读(314)  评论(0编辑  收藏  举报