一、前言
JavaWeb三大组件Servlet、Filter、Listener,其中之一便是过滤器Filter。
其实,Filter我们平常用的不多,一般多为项目初期搭建web架构的时候使用,后面用的就少了,在日常业务开发中不太可能碰到需要手写Filter的场景。
下面我们两带着以下几个问题,学习一下SpringBoot中怎样使用Filter。
- 在SpringBoot中如何配置Filter过滤器?
- 过滤器Filter在SpringBoot中的执行顺序是怎样的?
- 如何在SpringBoot中使用自定义的Filter过滤器?
二、Filter过滤器
1. Filter简介
Filter,过滤器,属于Servlet规范,并不是Spring独有的。Filter在web服务器中,位于浏览器与Servlet之间。
其作用是拦截一个请求,做一些业务逻辑操作,比如:登录校验、统一编码处理、敏感字符处理等。
2. 简介Filter流程
Filter在web服务器中,位于浏览器与Servlet之间,当请求进入web服务器时进行预处理,然后交Servlet,Servlet处理完成后,将响应传递给Filter进行后处理,之后才到达客户端的浏览器。
有时会设置多个Filter过滤器,经过Filter1处理后进入Filter2,之后也许还有Filter3,这样就形成了Filter链。而在Filter链中,只要有一处不放行,后续的检测都不会发生。
过滤器通过后,就可以访问web服务器上的资源。
三、自定义Filter过滤器
1. 引入web依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 自定义过滤器
第1步:创建过滤器类
定义一个类MyFilter,实现 Filter 接口,并重写其所有方法。
第2步:配置过滤器
在自定义过滤器MyFilter类上加 @WebFilter 注解,配置拦截资源的路径。
public class MyFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
说明:
- init方法:过滤器的初始化方法。在web服务器启动> 的时候会自动的创建Filter过滤器对象,在创建过滤> 器对象的时候会自动调用init初始化方法,这个方法> 只会被调用一次。
- doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。
- destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。
3. Filter的配置
在定义完Filter之后,Filter其实并不会生效,并不会被SpringBoot加载,还需要完成Filter的配置。
SpringBoot项目中,配置Filter生效有三种常见方式(注解和配置Bean):
- @WebFilter(如果存在多个Filter,则不推荐;单个Filter推荐。)
- FilterRegistrationBean注册方式
- 在web.xml中配置
3.1 方式一、@WebFilter注解方式
Filter的配置注解方式非常简单:
- 只需要在自定义过滤器MyFilter类上添加一个注解:@WebFilter,并指定属性urlPatterns(通过这个属性指定过滤器要拦截哪些请求);
- 在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。
注解方式加载-自定义过滤器:
@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class MyFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
启动类上加@ServletComponentScan注解:
@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}
注意: 注解方式配置Filter会有个小问题,即指定Filter的优先级比较麻烦,如果存在多个FIlter,则无法指定优先级。这个问题后续说明!!!
3.2 @WebFilter注解说明
@WebFilter注解属于Servlet3+,与Spring也没有什么关系。
@WebFilter注解常用属性如下,其中urlPatterns最为常用,表示这个filter适用于哪些url请求(默认场景下全部请求都被拦截)。
WebFilter常用属性:
属性名 | 类型 | 描述 |
---|---|---|
filterName | String | 指定过滤器的 name 属性,等价于 <filter-name> |
value | String[] | 该属性等价于 urlPatterns 属性。但是两者不应该同时使用。 |
urlPatterns | String[] | 指定一组过滤器的 URL 匹配模式。等价于 <url-pattern> 标签。比如:/*指的是所有请求。 |
servletNames | String[] | 指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中<servlet-name> 的取值。 |
dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams | WebInitParam[] | 指定一组过滤器初始化参数,等价于 <init-param> 标签。 |
asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于 <async-supported> 标签。 |
description | String | 该过滤器的描述信息,等价于 <description> 标签。 |
displayName | String | 该过滤器的显示名,通常配合工具使用,等价于 <display-name> 标签。 |
拦截路径(urlPatterns)说明:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /liupy/* | 访问/liupy下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
3.3 方式二、FilterRegistrationBean 注册方式
下面是使用包装bean注册方式。
首先需要新建一个配置类MybootWebConfig,该配置类中定义一个Bean,在该bean中注册自定义的过滤器。
自定义过滤器:
public class MyFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
自定义配置文件:
@Configuration
public class MybootWebConfig implement WebMvcConfig {
public MybootWebConfig(){}
@Bean
public FilterRegistrationBean<Filter> orderFilter() {
FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
filter.setName("reqFilter");
//注册自定义过滤器
filter.setFilter(new MyFilter()); //MyFilter是自定义过滤器
// 指定优先级
filter.setOrder(-1);
filter.addUrlPatterns(new String[]{"/*"});
return filter;
}
}
四、过滤器链
过滤器链:指的是在一个web应用程序当中,可以自定义并配置多个过滤器,多个过滤器就形成了一个过滤器链。
比如:在我们web服务器当中,定义了两个过滤器,这两个过滤器就形成了一个过滤器链。而这个链上的过滤器在执行的时候会一个一个的执行,会先执行第一个Filter,放行之后再来执行第二个Filter,如果执行到了最后一个过滤器放行之后,才会访问对应的web资源。
访问完web资源之后,按照我们刚才所介绍的过滤器的执行流程,还会回到过滤器当中来执行过滤器放行后的逻辑,而在执行放行后的逻辑的时候,顺序是反着的。先要执行过滤器2放行之后的逻辑,再来执行过滤器1放行之后的逻辑,最后在给浏览器响应数据。
五、Filter链的处理顺序
5.1. 指定FIlter顺序遇到的问题
上面我们说过,如果web项目中存在多个Filter过滤器(即过滤器链),注解方式在指定Filter过滤器的执行顺序时,很难控制执行顺序。
如果设置了多个Filter,如何设置多个Filter之间的处理顺序?有以下两种做法:
- 注解方式(不推荐):@Order(1)注解无法明确控制不同Filter的执行顺序,此时会按Filter类名字母顺序进行处理。如AFilter会先于BFilter执行; 如果只有一个Filter过滤器,则推荐,因为配置简单。
- web.xml方式(推荐):当请求到来时按照<filter-mapping></filter-mapping>中的顺序,从上到下进行处理。 当响应返回时,按照<filter-mapping></filter-mapping>中,从下到上进行处理(逆序)。
- 配置类中bean注册方式(推荐)。在配置类中,注册自定义Filter过滤器时,可以设置顺序:myFilter.setOrder(-1)。
5.2. 指定FIlter顺序演示
如下为自定义FIlter过滤器:
@Order(2)
@WebFilter
public class AnyFilter implements Filter, Ordered {
...
}
@Order(1)
@WebFilter
public class ReqFilter implements Filter, Ordered {
...
}
public class OrderFilter implements Filter {
}
/** 启动类 */
@ServletComponentScan
@SpringBootApplication
public class Application {
/** 注册自定义过滤器 OrderFilter*/
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("orderFilter");
filter.setFilter(new OrderFilter());
//设定优先级
filter.setOrder(-1);
return filter;
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
- 通过实际测试,@Order注解没有用,继承 Ordered接口也没有用,再不考虑web.xml配置的场景下,只能通过在注册Bean的时候指定优先级。
- 如上实例:三个Filter,两个通过@WebFilter注解方式注册,一个通过FilterRegistrationBean方式注册。
结论:
- 自定义Filter过滤器的优先级为:OrderFiler > AnyFilter >ReqFilter ;
- 显然,OrderFilter过滤器的优先级最高,因为OrderFilter是通过FilterRegistrationBean方式注册,并且手动注册时,并设置优先级为-1,所以先执行。
- AnyFilter 、ReqFilter 这两个自定义过滤器,都是注解方式,AnyFilter 设置为2,ReqFilter 设置为1,但是结果却是AnyFilter 优先级大于ReqFilter ,说明@Order注解不能指定Filter的优先级。
- 采用@WebFilter注解方式使用过滤器时,会按照 “自定义过滤器Filter类名字母先后顺序” 进行处理, 所以AnyFilter 会先于ReqFilter 执行。