SpringBoot项目中对定义的多个Aspect类排序
代码示例
@Configuration
public class AspectConfig {
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public static class LogAspect {
@Pointcut("execution(public * com.imooc.spring.web..*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println("LogAspect1 requestURI: " + request.getRequestURI());
}
}
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public static class LogAspect2 {
@Pointcut("execution(public * com.imooc.spring.web..*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println("LogAspect2 requestURI: " + request.getRequestURI());
}
}
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public static class LogAspect3 {
@Pointcut("execution(public * com.imooc.spring.web..*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println("LogAspect3 requestURI: " + request.getRequestURI());
}
}
}
定义3个切面Aspect类,通过 Order 注解来排序,值越小优先级越高。
测试controller如下
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/testStringParamTrim")
public TestObjectInfo testStringParamTrim(@RequestParam String goodsId, String goodsName) {
return new TestObjectInfo().setGoodsId(goodsId).setGoodsName(goodsName);
}
@Data
@Accessors(chain = true)
public static class TestObjectInfo {
private String goodsId;
private String goodsName;
}
}
遇到的问题
项目启动没报错,在执行时报以下错误
java.lang.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.
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.getJoinPointMatch(AbstractAspectJAdvice.java:658) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.18.jar:5.3.18]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.18.jar:5.3.18]
原因分析
具体原因为,Spring 会自动帮我们添加一个 ExposeInvocationInterceptor 拦截器到我们的拦截器列表,它的优先级为 PriorityOrdered.HIGHEST_PRECEDENCE + 1,我们自己定义的 Aspect 优先级不能高于它。它的作用是将 MethodInvocation 保存到 ThreadLocal 中,供后续的拦截器使用,所以必须第一个执行。
添加的流程:
- AbstractAdvisorAutoProxyCreator 的 getAdvicesAndAdvisorsForBean() 方法来获取符合要求的拦截器。
- AspectJAwareAdvisorAutoProxyCreator 的 extendAdvisors() 方法对我们的拦截器列表进行了扩展,添加了 ExposeInvocationInterceptor 实例。
- 使用 AspectJPrecedenceComparator 对拦截器列表进行排序,底层使用 AnnotationAwareOrderComparator,支持 Order 注解和 Ordered 接口。
解决方法
不要设置优先级为 Ordered.HIGHEST_PRECEDENCE,最高优先级为 Ordered.HIGHEST_PRECEDENCE + 1。
参考
Spring 源码阅读 70:容易被忽略的 ExposeInvocationInterceptor
Spring Aop 错误之:No MethodInvocation found ... the ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that advices with order HIGHEST