SpringMVCFilter过滤器AOP切面类Interceptors拦截器各自的执行顺序

Posted 写Bug的渣渣高

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVCFilter过滤器AOP切面类Interceptors拦截器各自的执行顺序相关的知识,希望对你有一定的参考价值。

文章内容:探究Filter过滤器、AOP切面类、interceptors,这几个类都有一个特性“拦截”(拦截器和过滤器实现的都是拦截功能,切面类是实现在某部分代码前执行特定代码,例如登录前要求用户验证)


提示:本文不深究Filter过滤器、AOP切面类、interceptors的原理,解释采用通俗易懂的方式进行解释,后续将针对三者写出详细的文章
点击跳转:【JavaWeb】Filter过滤器 的使用


  • 原生的Filter:可以实现以下功能
    • 调用目标资源之前,让一段代码执行。
    • 是否调用目标资源(即是否让用户访问web资源)。
    • 调用目标资源之后,让一段代码执行。

Filter(过滤器) 的基本功能是对 Servlet 容器调用 Servlet (JSP)的过程进行拦截, 从而在 Servlet 处理请求前和 Servlet 响应请求后实现一些特殊的功能。
点击跳转:AOP切面类详解

即在调用某个资源之前,能进行拦截


  • AOP切面类
    • 需要在核心业务前执行该辅助功能
    • 需要在核心业务执行之后执行该辅助功能
    • 需要在报错时候执行该辅助功能更
    • 需要在返回结果是执行该辅助功能
    • 需要在方法执行之前之后异常时执行该辅助功能

用动态代理来实现在执行某部分代码之前执行特定的代码
点击跳转:AOP切面类详解

需要注意:
返回通知(after-returning):当核心业务代码执行完成后执行,发生异常不执行
后置通知(after):不论目标方法是否发生异常都会执行,若无异常,则执行顺序在返回通知之后


  • Interceptor:拦截器:
    属于SpringMVC,实现的功能和过滤器一样,但是也有一些不同点
    • 工作平台不同:
      • 过滤器工作在 Servlet 容器中
      • 拦截器工作在 SpringMVC 的基础上

    • 拦截范围不同:
      • 过滤器:能够拦截到的最大范围是整个 Web 应用
      • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求

    • IOC容器的支持:
      • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
      • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

实现的功能和过滤器一样,开发使用的时候,假如可以用拦截器实现,就可以不用过滤器了



下面来研究,在存在多个过滤类/存在多个切面类/存在多个拦截器,其执行顺序

过滤器

1.准备工作:
准备两个过滤器(构成过滤器链),配置

public class Filter01 implements Filter 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
        Filter.super.init(filterConfig);
        System.out.println("Filter01:初始化");
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        System.out.println("Filter01:过滤");
        chain.doFilter(request, response);
    

    @Override
    public void destroy() 
        Filter.super.destroy();
        System.out.println("Filter01:过滤器销毁");
    


public class Filter02 implements Filter 

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
        Filter.super.init(filterConfig);
        System.out.println("Filter02:初始化");
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        System.out.println("Filter02:过滤");
        chain.doFilter(request,response);
    

    @Override
    public void destroy() 
        Filter.super.destroy();
        System.out.println("Filter02:过滤器销毁");

    


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <filter>
        <filter-name>Filter01</filter-name>
        <filter-class>com.example.demo_web.filter.Filter01</filter-class>
    </filter>
    <filter>
        <filter-name>Filter02</filter-name>
        <filter-class>com.example.demo_web.filter.Filter02</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Filter02</filter-name>
        <url-pattern>/TestServlet</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>Filter01</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

执行顺序:

Filter02:初始化
Filter01:初始化
[2022-02-03 04:31:29,250] Artifact demo_web:war exploded: Artifact is deployed successfully
[2022-02-03 04:31:29,250] Artifact demo_web:war exploded: Deploy took 991 milliseconds
Filter02:过滤
Filter01:过滤

执行顺序:过滤器链执行顺序和设置的filter-mapping先后顺序有关,先设置先过滤,和filter标签顺序无关
注意:使用多个过滤器时,构成过滤器链,假如在dofilter方法中如果没有 chain.doFilter(request,response);那么请求在进入第一个过滤器后无法通过



切面类

1.准备工作
两个切面类

@Aspect
@Component
public class TestAOP2 

    @Before(value = "execution(public int com.Calc.add(int ,int ))")
    public void printLogBefore(JoinPoint joinPoint)
        System.out.println("TEST2:Before");
    
    @AfterReturning(
            value = "execution(public int com.Calc.add(int ,int ))",
            returning = "returnValue"//定义的形参名字
    )

    public void printLogAfterSuccess(JoinPoint joinPoint,Object returnValue)
        System.out.println("TEST2:AfterReturning");
    

    @AfterThrowing(
            value ="execution(public int com.Calc.add(int ,int ))",
            throwing = "targetException"
    )
    public void printLogAfterException(JoinPoint joinPoint,Throwable targetException)
        System.out.println("TEST2:printLogAfterException");
    

    @After("execution(public int com.Calc.add(int ,int ))")
    public void printLogFinish()
        System.out.println("TEST2:After");
    

@Aspect
@Component
public class TestAOP3 

    @Before(value = "execution(public int com.Calc.add(int ,int ))")
    public void printLogBefore(JoinPoint joinPoint)
        System.out.println("TEST3:Before");
    
    @AfterReturning(
            value = "execution(public int com.Calc.add(int ,int ))",
            returning = "returnValue"
    )

    public void printLogAfterSuccess(JoinPoint joinPoint,Object returnValue)
        System.out.println("TEST3:AfterReturning");
    

    @AfterThrowing(
            value ="execution(public int com.Calc.add(int ,int ))",
            throwing = "targetException"
    )
    public void printLogAfterException(JoinPoint joinPoint,Throwable targetException)
        System.out.println("TEST3:printLogAfterException");
    

    @After("execution(public int com.Calc.add(int ,int ))")
    public void printLogFinish()
        System.out.println("TEST3:After");
    

无抛错情况下:

TEST2:Before
TEST3:Before
TEST3:AfterReturning
TEST3:After
TEST2:AfterReturning
TEST2:After

有抛错

TEST2:Before
TEST3:Before
TEST3:AfterReturning
TEST3:After
TEST2:AfterReturning
TEST2:After

执行顺序:
Spring 官方文档中是这样描述的:当在不同切面定义的两条相同类型通知都需要在同一连接点上运行时,除非另行指定,否则执行顺序是不确定的。 您可以通过指定优先级来控制执行顺序。 通过在切面类中实现 org.springframework.core.Ordered 接口或使用 @Order 注解对其进行注解。 给定两个切面,从 Ordered.getValue()(或 @Order 注解值)返回较低值的切面具有较高的优先级。
不过针对上面的描述,亲测,在不设定优先级的情况下,两个切面类执行顺序和其类名的排序有关,看下图,TestAOP2排序先于TestAOP3,TestAOP2就先执行

Test2和Test3,记住他们的类名排序顺序和结果

TEST2:Before
TEST3:Before
TEST3:AfterReturning
TEST3:After
TEST2:AfterReturning
TEST2:After

现在我们把Test3命名为Test1

执行结果

TEST1:Before
TEST2:Before
TEST2:AfterReturning
TEST2:After
TEST1:AfterReturning
TEST1:After


拦截器

1.准备工作:
两个拦截器,然后在spring-mvc文件中配置拦截器


    <mvc:interceptors>
            <bean class="com.mvc.interceptor.Interceptor01"/>
            <bean class="com.mvc.interceptor.Interceptor02"/>
    </mvc:interceptors>

public class Interceptor01 implements HandlerInterceptor 
    private Logger logger= LoggerFactory.getLogger(this.getClass());
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        logger.debug("Interceptor01:preHandle");

        return true;
    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception 
        logger.debug("Interceptor01:postHandle");

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
        logger.debug("Interceptor01:afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    

public class Interceptor02 implements HandlerInterceptor 

    private Logger logger= LoggerFactory.getLogger(this.getClass());
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
        logger.debug("Interceptor02:afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception 
        logger.debug("Interceptor02:postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        logger.debug("Interceptor02:preHandle");

        return true;
    


2.测试:

[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor01 - Interceptor01:preHandle
[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor02 - Interceptor02:preHandle
[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor02 - Interceptor02:postHandle
[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor01 - Interceptor01:postHandle
[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor02 - Interceptor02:afterCompletion
[http-nio-8080-exec-21] DEBUG com.mvc.interceptor.Interceptor01 - Interceptor01:afterCompletion

两个拦截器的顺序执行,其实是比较难记下来的。

  • preHandle()方法:和配置的顺序一样
  • 目标handler方法
  • postHandle()方法:和配置的顺序相反
  • 渲染视图
  • afterCompletion()方法:和配置的顺序相反

以上是关于SpringMVCFilter过滤器AOP切面类Interceptors拦截器各自的执行顺序的主要内容,如果未能解决你的问题,请参考以下文章

基于标注的AOP面向切面编程

Spring-AOP

SpringBoot切面控制业务逻辑

切面编程AOP之Castle.Core

过滤器拦截器AOP切面执行顺序的比较

Liferay7 BPM门户开发之36: 使用Portlet filters过滤器做切面AOP