Spring Aop 错误之:No MethodInvocation found ... the ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that advices with order HIGHEST

分析这个问题需要先了解一个东西:ExposeInvocationInterceptor

1.从官方得到以下相关信息:

 可以获知,当使用 AspectJ 时,spring 会添加一个连接器放到 advice 链的开头。但是为何要放这个东西呢?它是干嘛的?

2.看源码:

**
 * 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.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
@SuppressWarnings("serial")
public 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<MethodInvocation>("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!");
        return mi;
    }


    /**
     * Ensures that only the canonical instance can be created.
     */
    private ExposeInvocationInterceptor() {
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        MethodInvocation oldInvocation = invocation.get();
        invocation.set(mi); // 可知其实这个拦截器的作用就是把 MethodInvacation 放到了线程变量 ThreadLocal 中,这样子线程就可以通过线程变量轻松拿到 MethodInvacation 了
        try {
            return mi.proceed();
        }
        finally {
            invocation.set(oldInvocation);
        }
    }

    @Override
    public int getOrder() {
        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;
    }

}

看到源码上面的解释大概是这样的(纯Google翻译):

*暴露当前{@link org.aopalliance.intercept.MethodInvocation}的拦截器
*作为线程本地对象。 我们偶尔需要这样做; 例如,切入点时
*(例如,AspectJ表达式切入点)需要知道完整的调用上下文。
*
*除非确实有必要,否则不要使用此拦截器。 目标对象应该
*通常不了解Spring AOP,因为这会产生对Spring API的依赖。
*目标对象应尽可能是普通的POJO。
*
*如果使用,这个拦截器通常是拦截链中的第一个。

通过以上的了解,我们基本知道 ExposeInvocationInterceptor 这个拦截器是干嘛的,为啥要放到拦截链开头以及怎么让它一定会在拦截链开头的。但是还有这几点有点迷糊:偶尔需要这样做事咋样做呢?调用上下文是咋样调?

3.看栗子(来源标明出处:https://codeday.me/bug/20190410/930090.html,感谢作者的发文):

一般我们会有以下这种代码:

@Around(...)
    public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
         // some code
         Obj o =  pjp.proceed();
         // some code
    }

然后又会有下面这种代码:

private static ExecutorService executor = Executors.newCachedThreadPool();

@Around(...)
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {

Object obj = null;

Callable<Object> task = new Callable<Object>() {
    public Object call() {
        return pjp.proceed();
    }
};
Future<Object> future = executor.submit(task);
try {
    obj = future.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
    ...
} catch (InterruptedException e) {
    // we ignore this one...
} catch (ExecutionException e) {
    throw e.getCause(); // rethrow any exception raised by the invoked method
} finally {
    future.cancel(true); // may or may not desire this
}

return obj;
}

一旦改成第二种代码就要出问题了:

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

4.获得解决方案

  根据以上了解现在大致可以获知问题就出在 pjp.proceed(),即在 ExposeInvocationInterceptor  这个拦截器之前去执行了 proceed(); 导致拿不到 MethodInvocation。这就是没有保证 ExposeInvocationInterceptor 在拦截链开头导致的,综上所述只有保证了这个策略也就是Spring源码里说的那样就不会有问题,目前解决办法就是通过@Order来保证拦截器的执行顺序

原文地址:https://www.cnblogs.com/lzj123/p/11543396.html