如何调整AOP切面与Spring MVC拦截器的执行顺序?
这个问题的核心在于:HandlerInterceptor(拦截器)和AOP切面属于Spring MVC请求处理流程的两个完全独立的阶段,它们的执行时机本来就不在同一个链条上,@Order注解只能控制同类型组件的顺序(比如多个拦截器之间、多个切面之间),跨组件的顺序它管不了。
先给你理清楚默认的执行流程:
- 请求进入Servlet容器,先经过Filter链
- 到达DispatcherServlet,依次调用所有
HandlerInterceptor的preHandle方法(按拦截器的@Order顺序执行) - 接着DispatcherServlet调用目标控制器方法,这时候Spring AOP才会织入切面逻辑,执行
@Before通知 - 控制器方法执行完毕,调用
HandlerInterceptor的postHandle方法 - 最后视图渲染完成,调用
HandlerInterceptor的afterCompletion方法
看出来了吧?拦截器的preHandle是在控制器方法调用之前就触发了,而AOP的@Before是在控制器方法调用的瞬间才织入,所以默认拦截器逻辑必然先执行,哪怕你给AOP切面设了Ordered.HIGHEST_PRECEDENCE也没用——它们不在同一个排序池里。
根据你的需求(想让AOP先执行,或者让拦截器后执行),有几种不同的处理方式:
1. 把逻辑迁移到同类型组件(最推荐)
既然跨组件的顺序没法直接控制,那就把两个逻辑放到同一个类型的组件里,这样@Order就能生效了:
场景A:想让AOP逻辑先于拦截器执行
把AOP的逻辑迁移到Servlet Filter中。Filter的执行时机在拦截器之前,而且可以通过@Order控制顺序:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) // 让这个Filter最先执行 public class MyAnnotationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 从请求中拿到目标控制器方法 HttpServletRequest httpRequest = (HttpServletRequest) request; HandlerMethod handlerMethod = getHandlerMethod(httpRequest); // 处理你的自定义注解逻辑 if (handlerMethod != null && handlerMethod.hasMethodAnnotation(MyAspectAnnotation.class)) { // 原来AOP切面的逻辑放到这里 System.out.println("Filter执行了@MyAspectAnnotation的逻辑"); } // 继续执行后续Filter和拦截器 chain.doFilter(request, response); } // 从请求属性中获取HandlerMethod private HandlerMethod getHandlerMethod(HttpServletRequest request) { Object handler = request.getAttribute(DispatcherServlet.HANDLER_ATTRIBUTE); return handler instanceof HandlerMethod ? (HandlerMethod) handler : null; } }
这样这个Filter会在所有拦截器之前执行,相当于把原来的AOP逻辑提前到了拦截器之前。
场景B:想让拦截器逻辑后于AOP执行
把拦截器的逻辑迁移到AOP切面中,这样两个切面可以通过@Order控制顺序:
@Aspect @Order(2) // 设为比原来的AOP切面更低的优先级(数值越大越晚执行) @Component public class MyInterceptorAspect { @Pointcut("execution(* com.yourpackage..*Controller.*(..)) && @annotation(com.yourpackage.MyInterceptorAnnotation)") public void interceptorAnnotationPointcut() {} @Before("interceptorAnnotationPointcut()") public void beforeMethod(JoinPoint joinPoint) { // 原来拦截器preHandle的逻辑放到这里 System.out.println("AOP切面执行了@MyInterceptorAnnotation的逻辑"); } }
原来的AOP切面设@Order(1),这个新切面设@Order(2),就能保证原来的AOP逻辑先执行,再执行迁移过来的拦截器逻辑。
2. 调整拦截器的执行时机(适合业务允许的情况)
如果你的拦截器逻辑不需要在控制器方法执行前完成,可以把逻辑从preHandle移到postHandle或afterCompletion:
public class MyInterceptor extends HandlerInterceptorAdapter { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; if (handlerMethod.hasMethodAnnotation(MyInterceptorAnnotation.class)) { // 这里的逻辑会在控制器方法执行后、视图渲染前触发,此时AOP的@Before和@AfterReturning已经执行完毕 System.out.println("拦截器postHandle执行逻辑"); } } } }
注意:这种方式只适合不需要阻断请求的逻辑(比如日志、统计),如果是权限校验这类需要提前阻断请求的场景,就不能用这个方法。
3. 扩展DispatcherServlet(不推荐)
如果你一定要保留两个组件的原有类型,那只能通过扩展DispatcherServlet来修改请求处理流程,但这种方式会破坏Spring MVC的原有设计,维护成本很高,除非万不得已不建议使用。
内容的提问来源于stack exchange,提问作者fyrkov

