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 中,供后续的拦截器使用,所以必须第一个执行。

添加的流程:

  1. AbstractAdvisorAutoProxyCreator 的 getAdvicesAndAdvisorsForBean() 方法来获取符合要求的拦截器。
  2. AspectJAwareAdvisorAutoProxyCreator 的 extendAdvisors() 方法对我们的拦截器列表进行了扩展,添加了 ExposeInvocationInterceptor 实例。
  3. 使用 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

posted @ 2024-04-13 17:21  strongmore  阅读(144)  评论(0编辑  收藏  举报