前言:
在Spring框架中,过滤器(Filter)是一种用于对HTTP请求和响应进行预处理和后处理的组件。是 Servlet 规范规定的,在 Servlet 前执行的。过滤器可以用来执行一系列操作,例如身份验证、日志记录、请求修改等场景。
过滤器位于整个请求处理流程的最前端,因此在请求到达 Controller 层前,都会先被过滤器处理。过滤器可以拦截多个请求或响应,一个请求或响应也可以被多个过滤器拦截。
Filter基础介绍
Filter 引入了过滤链(Filter Chain)的概念,一个 Web 应用可以部署多个 Filter,这些 Filter 会组成一种链式结构,客户端的请求在到达 Servlet 之前会一直在这个链上传递,不同的 Filter 负责对请求/响应做不同的处理。 Filter 的处理流程如下图所示:
Filter 的作用可认为是对 Servlet 功能的增强,因为 Filter 可以对用户的请求做预处理,也可以对返回的响应做后处理,且这些处理逻辑与 Servlet 的处理逻辑是分隔开的,这使得程序中各部分业务逻辑之间的耦合度降低,从而提高了程序的可维护性和可扩展性。
如何创建过滤器
Filter 的生命周期对应的三个关键方法:
下面我们通过实现 Filter 接口来创建一个自定义的 Filter:
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(filterConfig.getFilterName() + " 被初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("Filter 拦截到了请求: " + request.getRequestURL());
System.out.println("Filter 对请求做预处理...");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter 修改响应的内容...");
}
@Override
public void destroy() {
System.out.println("Filter 被回收");
}
}
- init 方法的 filterConfig 参数封装了当前 Filter 的配置信息,在 Filter 初始化时,我们将 Filter 的名称打印在控制台。
- doFilter 方法定义了 Filter 拦截到用户请求后的处理逻辑,filterChain.doFilter(servletRequest, servletResponse); 指的是将请求传递给一下个 Filter 或 Servlet,如果不添加该语句,那么请求就不会向后传递,自然也不会被处理。在该语句之后,可以添加对响应的处理逻辑(如果要修改响应的 Header,可直接在该语句之前修改;如果要修改响应的内容,则需要在该语句之后,且需要自定义一个 response)。
- destroy 方法中,我们输出 "Filter 被回收" 的提示信息。
配置 Filter
Spring 项目中,我们可以使用 @Configuration + @Bean + FilterRegistrationBean 对 Filter 进行配置:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registryFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
registration.setFilter(new TestFilter());
registration.addUrlPatterns("/*");
registration.setName("TestFilter");
registration.setOrder(0);
return registration;
}
}
setFilter 方法用于设置 Filter 的类型;addUrlPatterns 方法用于设置拦截的规则;setName 方法用于设置 Filter 的名称;setOrder 方法用于设置 Filter 的优先级,数字越小优先级越高。
测试 Filter
定义一个简单的 Web 服务,测试 Filter 是否生效:
@RestController
public class UserController {
@RequestMapping(path = "/hello", method = RequestMethod.GET)
public String sayHello() {
System.out.println("正在处理请求...");
System.out.println("请求处理完成~");
return "I'm fine, thank you.";
}
}
启动项目,在浏览器中访问 localhost:8080/hello,等待请求处理完成,然后关闭项目。整个过程中,控制台依次打印了如下信息:
我们可以看到,自定义的 TestFilter 实现了拦截请求、处理响应的目标。接下来说说创建Filter的其他方式。
创建 Filter 的其它方式
1.@WebFilter 注解 + 包扫描
除了 FilterRegistrationBean 外,Servlet 3.0 引入的注解 @WebFilter 也可用于配置 Filter。我们只需要在自定义的 Filter 类上添加该注解,就可以设置 Filter 的名称和拦截规则:
@WebFilter(urlPatterns = "/*", filterName = "TestFilter")
public class TestFilter implements Filter {
// 省略部分代码
}
由于@WebFilter 并非 Spring 提供,因此若要使自定义的 Filter 生效,还需在配置类上添加 @ServletComponetScan 注解,并指定扫描的包:
@SpringBootApplication
@ServletComponentScan("com.example.filter")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@WebFilter 注解并不允许我们设置 Filter 的执行顺序,且在 Filter 类上添加 @Order 注解也是无效的。如果项目中有多个被 @WebFilter 修饰的 Filter,那么这些 Filter 的执行顺序由其 "类名的字典序" 决定,例如类名为 "Axx" 的 Filter 的执行顺序要先于类名为 "Bxx" 的 Filter。
2.@Component 注解
Spring 项目中,我们可以通过添加 @Component 注解将自定义的 Bean 交给 Spring 容器管理。同样的,对于自定义的 Filter,我们也可以直接添加 @Component 注解使其生效,而且还可以添加 @Order 注解来设置不同 Filter 的执行顺序。
@Component
@Order(1)
public class TestFilter implements Filter {
// 省略部分代码
}
此种配置方式一般不常使用,因为其无法设置 Filter 的拦截规则,默认的拦截路径为 /*。虽然不能配置拦截规则,但我们可以在 doFilter 方法中定义请求的放行规则,例如当请求的 URL 匹配我们设置的规则时,直接将该请求放行,也就是立即执行 filterChain.doFilter(servletRequest, servletResponse);。
3.继承 OncePerRequestFilter
OncePerRequestFilter 是一个由 Spring 提供的抽象类,在项目中,我们可以采用继承 OncePerRequestFilter 的方式创建 Filter,然后重写 doFilterInternal 方法定义 Filter 的处理逻辑,重写 shouldNotFilter 方法设置 Filter 的放行规则。对于多个 Filter 的执行顺序,我们也可以通过添加 @Order 注解进行设置。当然,若要使 Filter 生效,还需添加 @Component 注解将其注册到 Spring 容器。
@Component
@Order(1)
public class CSpringFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 处理逻辑
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
// 放行规则
}
}
以上分析并给出示例创建过滤器filter的几种方法。在项目中我们可以根据需要进行选择使用那种方法实现。下面说一下filter的优先级。
Filter 的优先级
使用配置类或添加 @Order 注解可以显式的设置 Filter 的执行顺序,修改类名可以隐式的设置 Filter 的执行顺序。如果项目中存在多个 Filter,且这些 Filter 由不同的方式创建,那么它们的执行顺序是怎样的呢?
能够确定的是,Spring 根据 Filter 的 order 决定其优先级,如果我们通过配置类或者通过 @Order 注解设置了 Filter 的 order,那么 order 值越小的 Filter 的优先级越高,无论 Filter 由何种方式创建。如果多个 Filter 的优先级相同,那么执行顺序为:
- 配置类中配置的 Filter 优先执行,如果配置类中存在多个 Filter,那么 Spring 按照其在配置类中配置的顺序依次执行。
- @WebFilter 注解修饰的 Filter 之后执行,如果存在多个 Filter,那么 Spring 按照其类名的字典序依次执行。
Filter 的应用场景
- 设置字符编码:字符编码 Filter 可以在 request 提交到 Servlet 之前或者在 response 返回给客户端之前为请求/响应设置特定的编码格式,以解决请求/响应内容乱码的问题。
- 记录日志:日志记录 Filter 可以在拦截到请求后,记录请求的 IP、访问的 URL,拦截到响应后记录请求的处理时间。当不需要记录日志时,也可以直接将 Filter 的配置注释掉。
- 校验权限:Web 服务中,客户端在发送请求时会携带 cookie 或者 token 进行身份认证,权限校验 Filter 可以在 request 提交到 Servlet 之前对 cookie 或 token 进行校验,如果用户未登录或者权限不够,那么 Filter 可以对请求做重定向或返回错误信息。
- 替换内容:内容替换 Filter 可以对网站的内容进行控制,防止输入/输出非法内容和敏感信息。例如在请求到达 Servlet 之前对请求的内容进行转义,防止 XSS 攻击;在 Servlet 将内容输出到 response 时,使用 response 将内容缓存起来,然后在 Filter 中进行替换,最后再输出到客户浏览器(由于默认的 response 并不能严格的缓存输出内容,因此需要自定义一个具备缓存功能的 response)。
- 解决跨域访问:前后端分离的项目往往存在跨域访问的问题,Filter 允许我们在 response 的 Header 中设置 "Access-Control-Allow-Origin"、"Access-Control-Allow-Methods" 等头域,以此解决跨域失败问题。
Filter 的实现原理
客户端的请求会在过滤链上依次传递,链上的每个 Filter 都会调用 doFilter 方法对请求进行处理。这个过程能够有条不紊的进行,底层依靠的是函数的回调机制。
回调函数
什么是回调函数?如果将函数(C++ 中的函数指针,Java 中的匿名函数、方法引用等)作为参数传递给主方法,那么这个函数就称为回调函数,主方法会在某一时刻调用回调函数。
使用回调函数的好处是能够实现函数逻辑的解耦,主方法内可以定义通用的处理逻辑,部分特定的操作则交给回调函数来完成。
Filter 的回调机制
上文我们自定义的 xxFilter 类需要实现 Filter 接口,Filter 接口的 doFilter 方法接收一个 FilterChain 类型的参数,这个 FilterChain 对象可认为是传递给 doFilter 方法的回调函数,严格来说应该是这个 FilterChain 对象的 doFilter 方法,注意这里提到了两个 doFilter 方法。Filter 接口的 doFilter 方法在执行结束或执行完某些步骤后会调用 FilterChain 对象的 doFilter 方法,即调用回调函数。重写 doFilter 方法:
public class TestFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// ...
filterChain.doFilter(servletRequest, servletResponse);
}
}
FilterChain 对象的实际类型为 ApplicationFilterChain,其 doFilter() 方法的处理逻辑如下(省略部分代码):
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// ...
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (pos < n) {
// 获取第 pos 个 filter, 即 xxFilter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
// ...
// 调用 xxFilter 的 doFilter 方法
filter.doFilter(request, response, this);
}
}
}
ApplicationFilterChain 的 doFilter 方法首先根据索引查询到我们定义的 xxFilter,然后调用 xxFilter 的 doFilter 方法,在调用时,ApplicationFilterChain 会将自己作为参数传递进去。xxFilter 的 doFilter 方法执行完某些步骤后,会调用回调函数,即 ApplicationFilterChain 的 doFilter 方法,这样 ApplicationFilterChain 就可以获取到下一个 xxFilter,并调用下一个 xxFilter 的 doFilter 方法,如此循环下去,直到所有的 xxFilter 全部被调用。
总结
本篇我们分析了spring中过滤器的前世今生,介绍了过滤器、过滤器的创建几种方法、过滤器的优先级和过滤器的原理简要的说明,可以在工作中作为参考使用。
本文暂时没有评论,来添加一个吧(●'◡'●)