*
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来保证拦截器的执行顺序