1 什么是过滤器
1.1 过滤器在一个请求的哪个环节?
过滤器是拦截请求和响应的。准确的说是在请求到达 controller(servlet)前,和响应返回给浏览器之前被过滤器拦截,过滤器可以对请求和响应做些处理。下图中的容器可以理解为 tomcat。
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 ......
入栈和出栈的痕迹很明显。