目录

  1. 概述
  2. 实现自定义的拦截器
    1. 通过 HandlerInterceptor 接口
    2. 通过 WebRequestInterceptor 接口
  3. 拦截器执行流程
    1. 单个拦截器的执行流程
    2. 多个拦截器的执行流程
  4. 拦截器与过滤器的关系
  5. 过滤器(Filter)
  6. 拦截器(Interceptor)
  7. 过滤器与拦截器的异同
    1. 实现原理不同
    2. 使用范围不同
    3. 触发时机不同
    4. 拦截的请求范围不同
    5. 控制执行顺序不同
    6. 总结
  8. 附录

任何优秀的 MVC 框架,都会提供一些通用的操作,如:请求数据的封装类型转换数据校验解析上传的文件防止表单的多次提交等。早期的 MVC 框架将这些操作都写死在核心控制器中,而这些常用的操作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低。拦截器作为 Spring MVC 中强大的控件,它可以在请求进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作

概述

SpringMVC 提供了 Interceptor 拦截器机制,类似于 Servlet 中的 Filter 过滤器,用于拦截用户的请求并做出相应的处理。比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录。Spring MVC 拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器

在 Spring MVC 中定义一个拦截器有两种方法:

  1. 实现 HandlerInterceptor 接口
  2. 实现 WebRequestInterceptor 接口

实现自定义的拦截器

通过 HandlerInterceptor 接口

HandlerInterceptor 位于 org.springframework.web.servlet 的包中,定义了三个方法,若要实现该接口,就要实现其三个方法:

  1. preHandle()方法:该方法在执行控制器方法之前执行。返回值为 Boolean 类型,如果返回 false,表示拦截请求,不再向下执行,如果返回 true,表示放行,程序继续向下执行(如果后面没有其他 Interceptor,就会执行 controller 方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理

  2. postHandle()方法:该方法在执行控制器方法调用之后,且在返回 ModelAndView 之前执行。由于该方法会在 DispatcherServlet 进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改

  3. afterCompletion()方法:该方法在执行完控制器之后执行,由于是在 Controller 方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作

实现了 HandlerInterceptor 接口之后,需要在 Spring 的类加载配置文件中配置拦截器实现类,才能使拦截器起到拦截的效果,加载配置有两种方式:

  1. 针对 HandlerMapping 配置

这里为 BeanNameUrlHandlerMapping 处理器配置了一个 interceptors 拦截器链,该拦截器链包含了 myInterceptor1 和 myInterceptor2 两个拦截器,具体实现分别对应下面 id 为 myInterceptor1 和 myInterceptor2 的 bean 配置

👍 此种配置的优点是针对具体的处理器映射器进行拦截操作
😣 缺点是如果使用多个处理器映射器,就要在多处添加拦截器的配置信息,比较繁琐

  1. 针对全局配置

在上面的配置中,可在 mvc:interceptors 标签下配置多个拦截器其子元素 bean 定义的是全局拦截器,它会拦截所有的请求;而 mvc:interceptor 元素中定义的是指定元素的拦截器,它会对指定路径下的请求生效,其子元素必须按照mvc:mapping --> mvc:exclude-mapping --> bean 的顺序,否则文件会报错

通过 WebRequestInterceptor 接口

WebRequestInterceptor 中也定义了三个方法,也是通过这三个方法来实现拦截的。这三个方法都传递了同一个参数 WebRequest, WebRequest 是 Spring 定义的一个接口,它里面的方法定义都基本跟 HttpServletRequest 一样,在 WebRequestInterceptor 中对 WebRequest 进行的所有操作都将同步到 HttpServletRequest 中,然后在当前请求中一直传递。三个方法如下:

  1. preHandle(WebRequest request) :WebRequestInterceptor 的该方法返回值为 void,不是 boolean。所以该方法不能用于请求阻断,一般用于资源准备。

  2. postHandle(WebRequest request, ModelMap model):preHandle 中准备的数据都可以通过参数 WebRequest 访问。ModelMap 是 Controller 处理之后返回的 Model 对象,可以通过改变它的属性来改变 Model 对象模型,达到改变视图渲染效果的目的。

  3. afterCompletion(WebRequest request, Exception ex) :Exception 参数表示的是当前请求的异常对象,如果 Controller 抛出的异常已经被处理过,则 Exception 对象为 null

拦截器执行流程

通过上文的步骤能够定义并使用拦截器,那么自定义拦截器的执行流程是什么?下文将进行详细的阐述

单个拦截器的执行流程

运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截的顺序相关。如果程序中只定义了一个拦截器,则该单个拦截器在程序中的执行流程如图所示

程序首先执行拦截器类中的 preHandle() 方法,如果该方法返回值是 true,则程序会继续向下执行处理器中的方法,否则不再向下执行;在业务控制器类 Controller 处理完请求后,会执行 postHandle()方法,而后会通过 DispatcherServlet 向客户端返回相应;在 DispatcherServlet 处理完请求后,才会执行 afterCompletion()方法

多个拦截器的执行流程

在一个 Web 工程中,甚至在一个 HandlerMapping 处理器适配器中都可以配置多个拦截器,每个拦截器都按照提前配置好的顺序执行。它们内部的执行规律并不像多个普通 Java 类一样,它们的设计模式是基于「 责任链 」的模式

下面通过图例来描述多个拦截器的执行流程,假设有两个拦截器 MyInterceptor1 和 MyInterceptor2,将 MyInterceptor1 配置在前,如图所示

拦截器与过滤器的关系

过滤器和拦截器都能够做到对相关的请求进行拦截,那么两者之间到底有什么不同?

过滤器(Filter)

过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法

  • init():该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用
  • doFilter():容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter
  • destroy():当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

System.out.println("Filter 前置");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

System.out.println("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

System.out.println("Filter 后置");
}
}

拦截器(Interceptor)

拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行,在 Spring Boot 中,将自定义好的拦截器处理类进行注册,并通过addPathPatternsexcludePathPatterns等属性设置需要拦截或需要排除的 URL

1
2
3
4
5
6
7
8
9
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}

过滤器与拦截器的异同

过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的

实现原理不同

过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于 Java 的反射机制(动态代理)实现的

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法

1
2
3
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}

private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}

}

而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChaindoFilter() 方法,以此循环执行实现函数回调

1
2
3
4
5
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

filterChain.doFilter(servletRequest, servletResponse);
}

使用范围不同

过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用

拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中

触发时机不同

过滤器拦截器的触发时机也不同

过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束

拦截的请求范围不同

请求正确访问的拦截顺序是:

1
Filter 处理中 --> Interceptor 前置 -> Controller Method -> Interceptor 处理中 -> Interceptor 处理后 -> Filter 处理中

过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用

控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行

1
2
3
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {}

拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}
/**
Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后
**/

看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行

总结

过滤器拦截器AOP 三者在选择通常要先明确 访问控制功能 的粒度,如果访问控制功能要精确到每个请求,那么要使用 AOPAOP 可以配置每个 Controller 的访问权限。而 拦截器过滤器 的粒度相对来说会粗一些,控制 HttpRequestHttpResponse 的访问。另外还有一点就是 过滤器 不能够使用 Spring 容器资源,只能在 Servlet 容器(e.g. tomcat)启动时调用一次,而 拦截器Spring 提供的组件,由 Spring 来管理,因此它能使用 Spring 里的任何资源、对象,例如 Service 对象、数据源、事务管理等等,通过 IoC 注入到 拦截器 中即可。相比较而言,拦截器 要更灵活一些。

附录

拦截器和过滤器 6 个区别
Spring MVC 拦截器
拦截器与过滤器的区别