1 什么是过滤器

1.1 过滤器在一个请求的哪个环节?

过滤器是拦截请求和响应的。准确的说是在请求到达 controller(servlet)前,和响应返回给浏览器之前被过滤器拦截,过滤器可以对请求和响应做些处理。下图中的容器可以理解为 tomcat。

springboot 过滤器获取所有请求参数 springboot filter过滤器_原理

1.2 过滤器是干什么用的

请求过滤器可以:做安全检查(用户认证)、重新格式化请求首部或者请求体、建立请求审计或日志。

响应过滤器可以:压缩响应流、追加或者修改响应流、创建一个完全不同的响应。

没有什么”请求过滤器“或者”响应过滤器“ 之分,请求被哪个过滤器拦截了,它的响应依旧会被该过滤器拦截,就像上图画的一样,请求怎么进 servlet 的,响应就得按原路返回。只是前后两次拦截后,在过滤器中执行的业务代码不一样而已,从而实现不同的目标。

上图中只画了一个 过滤器,实际上它是个过滤器“链”,里面有好多个小过滤器,按照指定顺序一个挨着一个排列,像被一条“线” 串起来一样,”线“的末尾会连接 servlet(或者叫 controller 中的某个方法)。

1.3 过滤器在代码里长什么样子?

// 源码,在官方 jar 包中。
public interface Filter {
	
	public void init(FilterConfig filterConfig) throws ServletException;

	public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
	
  public void destroy();
}

过滤器是个接口,实际使用时程序员自己 implement 这个接口,并且从写三个方法。

  • init 方法:web 服务启动时,过滤器会被加载一次,这个方法会被执行。(里面写什么,程序员自己来定)
  • destroy 方法:web 服务启动时,过滤器要被销毁,这个方法会被执行。(里面写什么,程序员自己来定)
  • doFilter 方法:请求(响应)被拦截是为了执行一些逻辑处理,那要执行什么逻辑处理呢?全部都写在这里。(依旧是程序员自己来定)

2 怎么定义过滤器

2.1 过滤器的关键要素以及定义方法

定义过滤器需要做四件事:

  • (1)创建类 Filter接口(public class CustomFilter1 implements Filter),并重写父类方法。你先得有个过滤器吧,源码中的接口和抽象类可是不能实例化的。
  • (2) 指定过滤器要拦截的请求,它是拦截一个请求?还是拦截好几个?还是拦截所有?这个得声明。请求怎么”穿进“ servlet 的,响应就得原路”穿出来“,所以声明拦截了请求,就是声明也拦截响应。
  • (3)指定过滤器的拦截顺序,当过滤器链中有多个过滤器是,谁先拦截谁后拦截,这个也得根据业务写清楚。(就是写个阿拉伯数字的事)。
  • (4)程序员自己个儿定义了过滤器,那 springboot 怎么知道呢?所以最后还得把定义的过滤器”告诉“ springboot。”告诉“ 有两种方式实现:一种叫”注册“,另一种叫”扫描“。

2.2 定义过滤器

”注册“的方式 (采用配置 Bean 方式)

创建过滤器,并重写父类方法。这里的 ”处理逻辑“ 就是打印一些信息。

public class CustomFilter1 implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 服务启动时,加载一次过滤器。
    System.out.println("--- custom filter 1 init ---");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
      /** 这块要写业务逻辑,这个就打印和信息吧 **/
    System.out.println(" custom filter 1 before  ...... ");
    chain.doFilter(request, response);
    // servlet 的 response 会来到这里。
    System.out.println(" custom filter 1 after  ...... ");

  }

  @Override
  public void destroy() {
    // 服务器变关闭时,销毁过滤器。
    System.out.println("--- custom filter 1 destroy ---");
  }
}

注册过滤器

@Configuration
public class ServletConfig {

  @Bean
  public FilterRegistrationBean<CustomFilter1> registerCustomFilter1(){
    FilterRegistrationBean customServlet1Bean = new FilterRegistrationBean<>();
    customServlet1Bean.setFilter(new CustomFilter1());
    customServlet1Bean.setName("customFilter1"); // 声明该过滤器的名字(可以不要)。
    customServlet1Bean.addUrlPatterns("/forward"); // 声明要拦截的请求。(只拦截"/forward" 的请求)
    customServlet1Bean.setOrder(1); // 声明该过滤器的拦截顺序。(第一个上手拦截)
    return customServlet1Bean;
  }
}

FilterRegistrationBean 根据名字就能看出来这货是用来注册过滤器的。@Configuration@Bean 组合使得 spring boot 在启动的时候会执行registerCustomFilter1() 方法,并且将返回值 customServlet1Bean 作为 bean 添加到 applicationContext 里面去。这样 Spring boot 就”知道了“ 程序员自定义的过滤器了。

”扫描“的方式 (采用注解方式)

@Order(2)
@WebFilter(filterName = "customFilter2", urlPatterns = "/forward")
public class CustomFilter2 implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("--- custom filter 2 init ---");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println(" custom filter 2 before  ...... ");
    chain.doFilter(request, response);
    System.out.println(" custom filter 2 after  ...... ");

  }

  @Override
  public void destroy() {
    System.out.println("--- custom filter 2 destroy ---");
  }
}

参照着看,这里自定义过滤器的同时,使用 @WebFilter 注解声明了要拦截的请求,@Order(2) 声明了拦截顺序。

@SpringBootApplication
@ServletComponentScan
public class FilterApplication {

  public static void main(String[] args) {
    SpringApplication.run(FilterApplication.class, args);
  }

}

给 Spring boot 程序的入口加上 @ServletComponentScan ,服务在启动时会自动扫描整个项目中带 @WebFilter 注解的类,为其创建一个 bean 并添加到 applicationContext 中。

(其实两种方式都是创建 bean,最后加入到 applicationContext 中)。

2.3 chain.doFilter(request, response) 做了什么事情?

在每个过滤器的 doFilter(*****) 方法中都会看到调用chain.doFilter(request, response) 方法,这是干什么呢?

前面提到过,实际使用中,过滤器按顺序被“线”串起来,并且“线”末尾连接一个 servlet。chain.doFilter(request, response) 就是将请求交给下一个过滤器的 doFilter(*****),如果当前过滤器是最后一个过滤器,那么 chain.doFilter(request, response) 就会将请求交给 servlet。 比如这里的顺序是:CustomFilter1 -> CustomFilter2 -> servlet 在 CustomFilter1 的 doFilter(*****) 中执行 chain.doFilter ,请求就会到 CustomFilter2 的 doFilter(*****) 方法里面,再执行 chain.doFilter 请求 就会到 servlet 中去。那 servlet 的 response 会按照:CustomFilter1 <- CustomFilter2 <- servlet 的顺序”穿出去“。其实”穿进来“、”穿出去“是 入栈和出栈。

3 场景测试

@GetMapping("/forward")
  public String filterForward(HttpServletRequest request,HttpServletResponse response){
    String method = request.getParameter("method");
    if (method == null){
      System.out.println("--- request straight to forward --- ");
      return "request straight to forward !";
    }
    System.out.println("hello "+ method);
    return "hello "+ method ;
  }

这个就是被拦截的 url。

启动服务:

2020-12-01 19:00:28.394  INFO 7004 --- [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 593 ms
--- custom filter 2 init ---
--- custom filter 1 init ---
2020-12-01 19:00:28.542  INFO 7004 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'

测试 curl

curl http://localhost:8080/forward

结果:

custom filter 1 before  ...... 
 custom filter 2 before  ...... 
--- request straight to forward --- 
 custom filter 2 after  ...... 
 custom filter 1 after  ......

入栈和出栈的痕迹很明显。