拦截器和过滤器
前言
过滤器(Filter)和拦截器(Interceptor)是 Web 开发中最常用也是最重要的两个组件,本文将先从各自的使用方式和示例程序入手,然后再比较二者的特点和使用场景。
过滤器
过滤器(Filter)是 Java EE 的 Servlet 规范中的一个组件,位于javax.servlet
包下,它的作用就是,一个 HTTP 请求到达 Servlet 组件前,提供一些处理机制,并且多个过滤器可以组成一条过滤器链,分别不执行不同的过滤逻辑。
换句话说,过滤器比 Servlet 更早的拿到 Request 的内容,能够对其中的内容进行校验或者修改,然后把处理好的 Request 传递给下一个(如果还有的话)过滤器,最终在所有过滤器都处理结束后,将最终的 Request 交付到 Servlet 手上。
等 Servlet 工作完成后,Servlet 交付的 Response 又会按照过滤器链的顺序,依次返回给每个过滤器处理,最终交付到客户端。
它工作的流程如下:
Filter 接口
想要实现一个 Filter 类,我们需要先了解javax.servlet.Filter
这个接口,它的源码如下:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.servlet;
import java.io.IOException;
/**
* A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static
* content), or on the response from a resource, or both. <br>
* <br>
* Filters perform filtering in the <code>doFilter</code> method. Every Filter has access to a FilterConfig object from
* which it can obtain its initialization parameters, a reference to the ServletContext which it can use, for example,
* to load resources needed for filtering tasks.
* <p>
* Filters are configured in the deployment descriptor of a web application
* <p>
* Examples that have been identified for this design are<br>
* 1) Authentication Filters <br>
* 2) Logging and Auditing Filters <br>
* 3) Image conversion Filters <br>
* 4) Data compression Filters <br>
* 5) Encryption Filters <br>
* 6) Tokenizing Filters <br>
* 7) Filters that trigger resource access events <br>
* 8) XSL/T filters <br>
* 9) Mime-type chain Filter <br>
*
* @since Servlet 2.3
*/
public interface Filter {
/**
* Called by the web container to indicate to a filter that it is being placed into service. The servlet container
* calls the init method exactly once after instantiating the filter. The init method must complete successfully
* before the filter is asked to do any filtering work.
* <p>
* The web container cannot place the filter into service if the init method either:
* <ul>
* <li>Throws a ServletException</li>
* <li>Does not return within a time period defined by the web container</li>
* </ul>
* The default implementation is a NO-OP.
*
* @param filterConfig The configuration information associated with the filter instance being initialised
*
* @throws ServletException if the initialisation fails
*/
default void init(FilterConfig filterConfig) throws ServletException {
}
/**
* The <code>doFilter</code> method of the Filter is called by the container each time a request/response pair is
* passed through the chain due to a client request for a resource at the end of the chain. The FilterChain passed
* in to this method allows the Filter to pass on the request and response to the next entity in the chain.
* <p>
* A typical implementation of this method would follow the following pattern:- <br>
* 1. Examine the request<br>
* 2. Optionally wrap the request object with a custom implementation to filter content or headers for input
* filtering <br>
* 3. Optionally wrap the response object with a custom implementation to filter content or headers for output
* filtering <br>
* 4. a) <strong>Either</strong> invoke the next entity in the chain using the FilterChain object
* (<code>chain.doFilter()</code>), <br>
* 4. b) <strong>or</strong> not pass on the request/response pair to the next entity in the filter chain to block
* the request processing<br>
* 5. Directly set headers on the response after invocation of the next entity in the filter chain.
*
* @param request The request to process
* @param response The response associated with the request
* @param chain Provides access to the next filter in the chain for this filter to pass the request and response
* to for further processing
*
* @throws IOException if an I/O error occurs during this filter's processing of the request
* @throws ServletException if the processing fails for any other reason
*/
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
/**
* Called by the web container to indicate to a filter that it is being taken out of service. This method is only
* called once all threads within the filter's doFilter method have exited or after a timeout period has passed.
* After the web container calls this method, it will not call the doFilter method again on this instance of the
* filter. <br>
* <br>
* This method gives the filter an opportunity to clean up any resources that are being held (for example, memory,
* file handles, threads) and make sure that any persistent state is synchronized with the filter's current state in
* memory. The default implementation is a NO-OP.
*/
default void destroy() {
}
}
先说说这个接口的文档中的一些要点:
- Filter 是一个普通的对象,用来执行过滤任务。
- 过滤的对象包括了请求(Request)以及响应(Response)。
- 请求的目标和响应的来源,叫资源(Resource),资源可以是 Servlet,也可以是静态内容(Static Content)。
- 一个 Filter 可以仅仅对请求做过滤处理,也可以仅仅对响应做过滤处理,也可以对请求和响应都做处理。
- 包含三个方法:init,doFilter,destroy。
- init 方法必须在 Filter 执行过滤任务前被 Web 容器成功执行。
- doFilter 方法是真正执行过滤任务代码的地方,方法参数包含一个 FilterChain,可以选择传递给下一个过滤器,也可以不传递,在当前的过滤器中终止请求。
- destroy 方法类似于 init 方法,由 Web 容器调用,表示从服务中移除该过滤器。
接下来,实现我们的第一个过滤器,MyFilter1:
package cn.korilweb.demofilterinterceptor.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Slf4j
@Component
public class MyFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) {
log.info("my filter 1 init()");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("my filter 1 doFilter() before {}", requestURL);
filterChain.doFilter(servletRequest, servletResponse);
log.info("my filter 1 doFilter() after, {}", requestURL);
}
@Override
public void destroy() {
log.info("my filter 1 destroy()");
}
}
为了查看过滤器的三个方法在 Web 应用中调用的顺序,每个方法都加了日志。
编写两个简单的 Controller,用于测试接口使用。
AdminController
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/page")
public String page() {
return "admin page!";
}
@GetMapping("/login")
public String login() {
return "admin login";
}
}
HomeController
@RestController
@RequestMapping("/home")
public class HomeController {
@GetMapping("/page")
public String page() {
return "home page!";
}
}
启动 SpringBoot 程序,可以在日志中看到,MyFilter1 的 init 方法首先被 Web 容器调用了
依次使用 curl 访问一下接口:
- curl.exe localhost:8080/admin/page
- curl.exe localhost:8080/home/page
- curl.exe localhost:8080/admin/login
- curl.exe localhost:8080/other/page
可以看到日志中,出现 8 条记录:
可以看到尽管我们没有编写 /other/page 这个路径对应的的 controller,Filter 依然执行了,它并不关心藏在过滤器链末尾的资源是 servlet 还是静态资源,也不关心它们是否存在,都会执行过滤任务。
在 doFilter 方法中,执行了 filterChain.doFilter 方法,并且传递了 request 和 response 对象,表示交给过滤器链的下一个过滤器处理,如果我们想在某个过滤器中终止请求,不调用 filterChain.doFilter 方法即可,比如:如果请求头不包含 access 或者 access 的值不为 true,就禁止访问,可以这么写:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
StringBuffer requestURL = request.getRequestURL();
log.info("my filter 1 doFilter() before {}", requestURL);
// 获取请求头中的 access 参数
String access = request.getHeader("access");
// 如果 access 不存在,或者存在但不等于 true,则禁止访问
if (Objects.isNull(access) || !Objects.equals(access, "true")) {
log.info("forbidden access: {}", requestURL);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
log.info("my filter 1 doFilter() after, {}", requestURL);
}
访问以下接口:
- curl.exe localhost:8080/admin/login
- curl.exe -H “access: false” localhost:8080/admin/login
- curl.exe -H “access: true” localhost:8080/admin/login
可以看到,只有最后一次请求,成功返回了响应内容,第一个和第二个请求都被禁止了,换句话说,Filter 就像门卫,过滤掉那些不符合条件的请求,如果都满足要求,那么可以执行 filterChain.doFilter 进行放行处理(移交到下一个过滤器)。
多个过滤器
我们再编写第二个过滤器,MyFilter2:
package cn.korilweb.demofilterinterceptor.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Slf4j
@Component
public class MyFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) {
log.info("my filter 2 init()");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
StringBuffer requestURL = request.getRequestURL();
log.info("my filter 2 doFilter() before {}", requestURL);
filterChain.doFilter(servletRequest, servletResponse);
log.info("my filter 2 doFilter() after {}", requestURL);
}
@Override
public void destroy() {
log.info("my filter 2 destroy()");
}
}
代码和 MyFilter1 基本一致,这里多个过滤器就存在一个顺序问题,谁先谁后?
如果要定义它们的顺序,需要使用 @Order 注解,数字越小,优先级越高,比如我希望 MyFilter1 先于 MyFilter2 执行:
@Component
@Order(1)
public class MyFilter1 implements Filter {
// some code
}
@Component
@Order(2)
public class MyFilter2 implements Filter {
// some code
}
执行接口调用,日志打印如下:
@WebFilter + @ServletComponentScan
@Component 虽然使用简单,但是有很大的局限性,那就是它会注册成一个 url pattern 为 “/*” 的过滤器,也就是说,每一个路径接口都会执行你自定义的过滤器。
但很多时候,我们希望控制的粒度更细些,比如:/home/page 不走过滤器,而 /admin/* 的所有接口都要经过过滤器校验。
这时候可以使用 @WebFilter 注解,里面的 urlPatterns 参数,可以定义那些访问路径需要走这个过滤器。
除了 @WebFilter,还需要在 SpringBoot 启动类上增加 @ServletComponentScan 注解,才能生效。
比如,现在希望所有请求都经过 MyFilter2,但 /admin/* 的请求在经过 MyFilter2 前还需要额外经过 MyFilter1,可以这么写:
@WebFilter(urlPatterns = "/admin/*")
public class MyFilter1 implements Filter {
// some code
}
@WebFilter
public class MyFilter2 implements Filter {
// some code
}
// 启动类需要加 @ServletComponentScan 注解
@ServletComponentScan
@SpringBootApplication
public class DemoFilterInterceptorApplication {
public static void main(String[] args) {
SpringApplication.run(DemoFilterInterceptorApplication.class, args);
}
}
执行以下接口:
- curl.exe localhost:8080/admin/login
- curl.exe localhost:8080/admin/page
- curl.exe localhost:8080/home/page
结果显示,前两个会先调用 MyFilter1,然后调用 MyFilter2,最后一个接口,仅仅调用 MyFilter2。
@Component + @Order 可以定义顺序,但是无法使用 url pattern,而 @WebFilter + @ServletComponentScan 虽然可以使用 url pattern 定义那些接口走特定的过滤器,但是又无法准确定义顺序了。
FilterRegistrationBean
以上的两种方式各有优缺点,如果希望控制顺序,定义命名,设置 url pattern,最好使用 FilterRegistrationBean 的方式,首先删除 MyFilter1 和 MyFilter2 以及启动类上之前加过的 @Component、@WebFilter、@Order、@ServletComponentScan 等注解。
然后,定义一个配置类:
package cn.korilweb.demofilterinterceptor.config;
import cn.korilweb.demofilterinterceptor.filter.MyFilter1;
import cn.korilweb.demofilterinterceptor.filter.MyFilter2;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter1> myFilter1RegistrationBean() {
FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter1());
bean.setName("myFilter1");
bean.setOrder(1);
bean.addUrlPatterns("/admin/*");
return bean;
}
@Bean
public FilterRegistrationBean<MyFilter2> myFilter2RegistrationBean() {
FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter2());
bean.setName("myFilter2");
bean.setOrder(2);
bean.addUrlPatterns("/*");
return bean;
}
}
在配置类中可以精确定义路径和顺序。
总结一下过滤器的三种使用方式的特点:
- @Component + @Order:可以定义过滤器顺序,但无法定义 url pattern。
- @WebFilter + @ServletComponentScan:可以定义 url pattern,但无法定义顺序。
- FilterRegistrationBean:过滤器顺序和 url pattern 都可以定义。
拦截器
拦截器的作用跟过滤器很相似,但是也有很多不同的地方,比如:过滤器是 Servlet API 的组件,而拦截器是 SpringMVC 的组件,过滤器的执行要先于拦截器。
HandlerInterceptor
和过滤器一样,拦截器也有一个接口需要实现——HandlerInterceptor:
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
/**
* Workflow interface that allows for customized handler execution chains.
* Applications can register any number of existing or custom interceptors
* for certain groups of handlers, to add common preprocessing behavior
* without needing to modify each handler implementation.
*
* <p>A HandlerInterceptor gets called before the appropriate HandlerAdapter
* triggers the execution of the handler itself. This mechanism can be used
* for a large field of preprocessing aspects, e.g. for authorization checks,
* or common handler behavior like locale or theme changes. Its main purpose
* is to allow for factoring out repetitive handler code.
*
* <p>In an asynchronous processing scenario, the handler may be executed in a
* separate thread while the main thread exits without rendering or invoking the
* {@code postHandle} and {@code afterCompletion} callbacks. When concurrent
* handler execution completes, the request is dispatched back in order to
* proceed with rendering the model and all methods of this contract are invoked
* again. For further options and details see
* {@code org.springframework.web.servlet.AsyncHandlerInterceptor}
*
* <p>Typically an interceptor chain is defined per HandlerMapping bean,
* sharing its granularity. To be able to apply a certain interceptor chain
* to a group of handlers, one needs to map the desired handlers via one
* HandlerMapping bean. The interceptors themselves are defined as beans
* in the application context, referenced by the mapping bean definition
* via its "interceptors" property (in XML: a <list> of <ref>).
*
* <p>HandlerInterceptor is basically similar to a Servlet Filter, but in
* contrast to the latter it just allows custom pre-processing with the option
* of prohibiting the execution of the handler itself, and custom post-processing.
* Filters are more powerful, for example they allow for exchanging the request
* and response objects that are handed down the chain. Note that a filter
* gets configured in web.xml, a HandlerInterceptor in the application context.
*
* <p>As a basic guideline, fine-grained handler-related preprocessing tasks are
* candidates for HandlerInterceptor implementations, especially factored-out
* common handler code and authorization checks. On the other hand, a Filter
* is well-suited for request content and view content handling, like multipart
* forms and GZIP compression. This typically shows when one needs to map the
* filter to certain content types (e.g. images), or to all requests.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerExecutionChain#getInterceptors
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping#setInterceptors
* @see org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor
* @see org.springframework.web.servlet.i18n.LocaleChangeInterceptor
* @see org.springframework.web.servlet.theme.ThemeChangeInterceptor
* @see javax.servlet.Filter
*/
public interface HandlerInterceptor {
/**
* Interception point before the execution of a handler. Called after
* HandlerMapping determined an appropriate handler object, but before
* HandlerAdapter invokes the handler.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically sending an HTTP error or writing a custom response.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation returns {@code true}.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
* @throws Exception in case of errors
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* Interception point after successful execution of a handler.
* Called after HandlerAdapter actually invoked the handler, but before the
* DispatcherServlet renders the view. Can expose additional model objects
* to the view via the given ModelAndView.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception in case of errors
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allows
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's {@code preHandle}
* method has successfully completed and returned {@code true}!
* <p>As with the {@code postHandle} method, the method will be invoked on each
* interceptor in the chain in reverse order, so the first interceptor will be
* the last to be invoked.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param ex any exception thrown on handler execution, if any; this does not
* include exceptions that have been handled through an exception resolver
* @throws Exception in case of errors
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
拦截器提供了三个方法,preHandler 是在进入具体的 Controller 代码前执行,postHandler 是在 Controller 代码结束后,返回 ModelAndView 的时候执行,而 afterCompletion 用于请求结束后的一些操作,比如资源的清理和日志的记录。
接下来,定义两个拦截器,MyInterceptor1:
package cn.korilweb.demofilterinterceptor.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("my interceptor 1 preHandler() {}", request.getRequestURL());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("my interceptor 1 postHandler() {}", request.getRequestURL());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("my interceptor 1 afterCompletion() {}", request.getRequestURL());
}
}
MyInterceptor2:
package cn.korilweb.demofilterinterceptor.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("my interceptor 2 preHandler() {}", request.getRequestURL());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("my interceptor 2 postHandler() {}", request.getRequestURL());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("my interceptor 2 afterCompletion() {}", request.getRequestURL());
}
}
定义好拦截器后,需要在配置类中进行配置:
package cn.korilweb.demofilterinterceptor.config;
import cn.korilweb.demofilterinterceptor.interceptor.MyInterceptor1;
import cn.korilweb.demofilterinterceptor.interceptor.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor1());
registry.addInterceptor(new MyInterceptor2());
}
}
拦截器的顺序就取决于配置类中 addInterceptor 的顺序。
接下来访问接口:curl.exe localhost:8080/admin/page
日志如下:
可以看到,interceptor 包裹着 controller 的代码,而 filter 又包裹着 interceptor。
在配置文件中,可以指定路径匹配(path pattern),类似过滤器的 url pattern。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// MyIntercaptor1 作用于 /admin/*
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/admin/*");
// MyIntercaptor2 作用于 /home/*
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/home/*");
}
}
过滤器和拦截器
差异
在执行代码的层面上,过滤器处于 Servlet 层,过滤器在请求到达 Servlet 之前执行,以及在 Servlet 处理结束后执行。而拦截器位于 Controller 层。
这意味着,过滤器能修改的仅仅是 Request 和 Response,而拦截器更靠近 Controller,还能够修改 ModelAndView。
在框架上,过滤器属于 Servlet API 组件,拦截器属于 Spring 框架的组件。
在接口上,过滤器需要实现 javax.servlet.Filter 接口,而拦截器需要实现 org.springframework.web.servlet.HandlerInterceptor。
执行顺序
- Request 发送到 Spring Boot 程序
- Request 进入 Filter Chain,过滤器链上的每个过滤器都能修改 Request 和 Response
- 一旦 Request 通过了 Filter Chain,接下来将抵达 DispatcherServlet。
- DespatcherServlet 通过 HandlerMapping 找到于请求路径匹配的 Controller。
- 在执行 Controller 相应方法前,Request 将进入 Interceptor Chain。
- 一旦 Request 通过了 Interceptor Chain,Controller 对应的方法将被执行。
- Controller 生成 Response,并且沿着 Interceptor Chain 原路返回。
- Response 通过了 Interceptor Chain 之后,继续沿着 Filter Chain 原路返回。
下面是流程图:
总结
Spring Interceptor 和 Servlet Filters 都用于拦截 Web 应用进程中的Request 和 Response。
拦截器在 Controller 级别运行,而过滤器在 Servlet 级别运行。
拦截器对请求和响应具有更精细的控制,而过滤器对请求和响应具有更一般的控制。
拦截器和过滤器都是用于自定义和增强 Web 应用的有用工具,在选择要在应用中使用的工具时,了解它们之间的差异非常重要。