SpringBoot过滤器Filter使用指南
|Word count:2.3k|Reading time:9min|Post View:
I. 背景
在正式开始之前,有必要先简单看一下什么是 Filter(过滤器),以及这个有什么用
1. Filter 说明
Filter,过滤器,属于 Servlet 规范,并不是 Spring 独有的。其作用从命名上也可以看出一二,拦截一个请求,做一些业务逻辑操作,然后可以决定请求是否可以继续往下分发,落到其他的 Filter 或者对应的 Servlet
简单描述下一个 http 请求过来之后,一个 Filter 的工作流程:
- 首先进入 filter,执行相关业务逻辑
- 若判定通行,则进入 Servlet 逻辑,Servlet 执行完毕之后,又返回 Filter,最后在返回给请求方
- 判定失败,直接返回,不需要将请求发给 Servlet
插播一句:上面这个过程,和 AOP 中的@Around
环绕切面的作用差不多
2. 项目搭建
接下来我们搭建一个 web 应用方便后续的演示,借助 SpringBoot 搭建一个 web 应用属于比较简单的活;
创建一个 maven 项目,pom 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7</version> <relativePath/><!-- lookup parent from update --> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency> </dependencies>
<build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
|
II. Filter 教程
1. 使用说明
在 SpringBoot 项目中,如果需要自定义一个 Filter,并没有什么特殊的地方,直接实现接口即可,比如下面一个输出请求日志的拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Slf4j @WebFilter publicclass ReqFilter implements Filter { public ReqFilter() { System.out.println("init reqFilter"); }
@Override public void init(FilterConfig filterConfig) throws ServletException { }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; log.info("url={}, params={}", req.getRequestURI(), JSON.toJSONString(req.getParameterMap())); chain.doFilter(req, response); }
@Override public void destroy() { } }
|
实现一个自定义的 Filter 容易,一般有两个步骤
- 实现 Filter 接口
- 在
doFilter
方法中添加业务逻辑,如果允许访问继续,则执行chain.doFilter(req, response);
;不执行上面这一句,则访问到此为止
接下来的一个问题就是如何让我们自定义的 Filter 生效,在 SpringBoot 项目中,有两种常见的使用方式
- @WebFilter
- 包装 Bean:
FilterRegistrationBean
a. WebFilter
这个注解属于 Servlet3+,与 Spring 也没有什么关系,所以问题来了,当我在 Filter 上添加了这个注解之后,Spring 怎么让它生效呢?
- 配置文件中显示使用注解
@ServletComponentScan
1 2 3 4 5 6 7 8
| @ServletComponentScan @SpringBootApplication publicclass Application {
public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
WebFilter 常用属性如下,其中urlPatterns
最为常用,表示这个 filter 适用于哪些 url 请求(默认场景下全部请求都被拦截)
属性名 |
类型 |
描述 |
filterName |
String |
指定过滤器的 name 属性,等价于 |
value |
String[] |
该属性等价于 urlPatterns 属性。但是两者不应该同时使用。 |
urlPatterns |
String[] |
指定一组过滤器的 URL 匹配模式。等价于标签。 |
servletNames |
String[] |
指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中的取值。 |
dispatcherTypes |
DispatcherType |
指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams |
WebInitParam[] |
指定一组过滤器初始化参数,等价于标签。 |
asyncSupported |
boolean |
声明过滤器是否支持异步操作模式,等价于标签。 |
description |
String |
该过滤器的描述信息,等价于标签。 |
displayName |
String |
该过滤器的显示名,通常配合工具使用,等价于标签。 |
b. FilterRegistrationBean
上面一种方式比较简单,后面会说到有个小问题,指定 Filter 的优先级比较麻烦,
下面是使用包装 bean 注册方式
1 2 3 4 5 6 7 8 9
| @Bean public FilterRegistrationBean<OrderFilter> orderFilter() { FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>(); filter.setName("reqFilter"); filter.setFilter(new ReqFilter()); // 指定优先级 filter.setOrder(-1); return filter; }
|
2. 常见问题
上面整完,就可以开始测试使用过滤器了,在进入实测环节之前,先来看两个常见的问题
- Filter 作为 Servelt 的组件,怎么与 SpringBoot 中的 Bean 交互
- 多个 Filter 之间的优先级怎么确定
a. Filter 依赖 Bean 注入问题
如果有小伙伴使用 SpringMVC + web.xml 方式来定义 Filter,就会发现自定义的 Filter 中无法通过@Autowired
方式来注入 Spring 的 bean
我之前使用的是 spring4 Servlet2+ ,存在上面的问题,如果有不同观点请留言告诉我,感谢
SpringBoot 中可以直接注入依赖的 Bean,从上面的第二种注册方式可以看到,Spring 将 Filter 封装成了一个 Bean 对象,因此可以直接注入依赖的 Bean
下面定义一个AuthFilter
,依赖了自定义的DemoBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @Data @Component publicclass DemoBean { privatelong time;
public DemoBean() { time = System.currentTimeMillis(); }
public void show() { System.out.println("demo bean!!! " + time); } }
@Slf4j @WebFilter publicclass AuthFilter implements Filter { @Autowired private DemoBean demoBean;
public AuthFilter() { System.out.println("init autFilter"); }
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("in auth filter! {}", demoBean); // 测试,用header中的 tx-demo 来判断是否为认证的请求 HttpServletRequest req = (HttpServletRequest) request; String auth = req.getHeader("tx-demo"); if ("yihuihui".equals(auth)) { // 只有认证的请求才允许访问,请求头中没有这个时,不执行下面的的方法,则表示请求被过滤了 // 在测试优先级时打开下面的注释 // chain.doFilter(request, response); } else { chain.doFilter(request, response); } }
@Override public void destroy() {
} }
|
b. 优先级指定
Filter 的优先级指定,通过我的实际测试,@Order
注解没有用,继承 Ordered
接口也没有用,再不考虑 web.xml 的场景下,只能通过在注册 Bean 的时候指定优先级
实例如下,三个 Filter,两个通过@WebFilter
注解方式注册,一个通过FilterRegistrationBean
方式注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Slf4j @Order(2) @WebFilter publicclass AuthFilter implements Filter, Ordered { ... }
@Slf4j @Order(1) @WebFilter publicclass ReqFilter implements Filter, Ordered { ... }
@Slf4j publicclass OrderFilter implements Filter { }
@ServletComponentScan @SpringBootApplication publicclass Application { @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); }
}
|
3. 测试
上面定义了三个 Filter,我们主要验证下优先级,如果@Order
注解生效,那么执行的先后顺序应该是
1
| OrderFilter -> ReqFilter -> AuthFilter
|
如果不是上面的顺序,那么说明@Order
注解没有用
1 2 3 4 5 6 7
| @RestController publicclass IndexRest { @GetMapping(path = {"/", "index"}) public String hello(String name) { return"hello " + name; } }
|
从org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
可以看出 AuthFilter
的优先级大于ReqFilter
, 下面实际的输出也说明了@Order
注解不能指定 Filter 的优先级(不知道为什么网络上有大量使用 Order 来指定 Filer 优先级的文章!!!)
接下来我们的问题就是WebFilter
注解来注册的 Filter 的优先级是怎样的呢,我们依然通过 code来看,关键代码路径为: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
- OrderFiler 是我们手动注册并设置优先级为-1
- ReqFilter, AuthFilter 通过 WebFillter 方式注册,默认优先级为
2147483647
,相同优先级的情况下,根据名字先后顺序来决定
III. 小结
本文主要介绍了过滤器 Filter 的使用方式,以及常见的两个问题解答,文中内容穿插了一点源码的分析截图,并未深入,如有兴趣的同学可以根据文中提的几个关键位置探索一番
下面简单小结下文中内容
1. Filter 使用
自定义 Filter 的实现
- 实现 Filter 接口
- doFilter 方法中,显示调用
chain.doFilter(request, response);
表示请求继续;否则表示请求被过滤
注册生效
@ServletComponentScan
自动扫描带有@WebFilter
注解的 Filter
- 创建 Bean:
FilterRegistrationBean
来包装自定义的 Filter
2. IoC/DI
在 SpringBoot 中 Filter 可以和一般的 Bean 一样使用,直接通过Autowired
注入其依赖的 Spring Bean 对象
3. 优先级
通过创建FilterRegistrationBean
的时候指定优先级,如下
1 2 3 4 5 6 7 8
| @Bean public FilterRegistrationBean<OrderFilter> orderFilter() { FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>(); filter.setName("orderFilter"); filter.setFilter(new OrderFilter()); filter.setOrder(-1); return filter; }
|
此外格外注意, @WebFilter
声明的 Filter,优先级为2147483647
(最低优先级)
- @Order 注解不能指定 Filter 优先级
- @Order 注解不能指定 Filter 优先级
- @Order 注解不能指定 Filter 优先级