SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇
前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filter 的优先级可以直接通过@Order注解来指定;最后将从源码的角度分析一下两种不同的使用方式下,为什么@Order注解一个生效,一个不生效
本篇博文强烈推荐与上一篇关联阅读,可以 get 到更多的知识点: 191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南
I. Filter
本篇博文的工程执行的环境依然是SpringBoot2+, 项目源码可以在文章最后面 get
1. 使用姿势
前面一篇博文,介绍了两种使用姿势,下面简单介绍一下
WebFilter 注解
在 Filter 类上添加注解@WebFilter;然后再项目中,显示声明@ServletComponentScan,开启 Servlet 的组件扫描
@WebFilter
public class SelfFilter implements Filter {
}
@ServletComponentScan
public class SelfAutoConf {
}
FilterRegistrationBean
另外一种方式则是直接创建一个 Filter 的注册 Bean,内部持有 Filter 的实例;在 SpringBoot 中,初始化的是 Filter 的包装 Bean 就是这个
@Bean
public FilterRegistrationBean<OrderFilter> orderFilter() {
FilterRegistrationBean<OrderFilter> filter = new FilterRegistrationBean<>();
filter.setName("orderFilter");
filter.setFilter(new SelfFilter());
filter.setOrder(-1);
return filter;
}
本篇将介绍另外一种方式,直接将 Filter 当做普通的 Bean 对象来使用,也就是说,我们直接在 Filter 类上添加注解@Component即可,然后 Spring 会将实现 Filter 接口的 Bean 当做过滤器来注册
而且这种使用姿势下,Filter 的优先级可以通过@Order注解来指定;
设计一个 case,定义两个 Filter(ReqFilter和OrderFilter), 当不指定优先级时,根据名字来,OrderFilter 优先级会更高;我们主动设置下,希望ReqFilter优先级更高
@Order(1)
@Component
public class ReqFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("req filter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
@Order(10)
@Component
public class OrderFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("order filter!");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
2. 优先级测试
上面两个 Filter 直接当做了 Bean 来写入,我们写一个简单的 rest 服务来测试一下
@RestController
public class IndexRest {
@GetMapping(path = {"/", "index"})
public String hello(String name) {
return "hello " + name;
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
请求之后输出结果如下, ReqFilter 优先执行了

II. 源码分析
当我们直接将 Filter 当做 Spring Bean 来使用时,@Order注解来指定 Filter 的优先级没有问题;但是前面一篇博文中演示的@WebFilter注解的方式,则并不会生效
- 这两种方式的区别是什么?
@Order注解到底有什么用,该怎么用
1. Bean 方式
首先我们分析一下将 Filter 当做 Spring bean 的使用方式,我们的目标放在 Filter 的注册逻辑上
第一步将目标放在: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
下面的逻辑中包括了 ServeltContext 的初始化,而我们的 Filter 则可以看成是属于 Servlet 的 Bean
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
注意上面代码中的 for 循环,在执行getServletContextInitializerBeans()的时候,Filter 就已经注册完毕,所以我们需要再深入进去
将目标集中在org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
this.initializers = new LinkedMultiValueMap<>();
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values()
.stream()
.flatMap((value) -> value.stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
}
上面有两行代码比较突出,下面单独捞出来了,需要我们重点关注
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
通过断点进来,发现第一个方法只是注册了dispatcherServletRegistration;接下来重点看第二个
@SuppressWarnings("unchecked")
private void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
addAsRegistrationBean(beanFactory, Servlet.class,
new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class,
new FilterRegistrationBeanAdapter());
for (Class<?> listenerType : ServletListenerRegistrationBean
.getSupportedTypes()) {
addAsRegistrationBean(beanFactory, EventListener.class,
(Class<EventListener>) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
从上面调用的方法命名就可以看出,我们的 Filter 注册就在addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());

上面的截图就比较核心了,在创建FilterRegistrationBean的时候,根据 Filter 的顺序来指定最终的优先级
然后再回到构造方法中,根据 order 进行排序, 最终确定 Filter 的优先级

2. WebFilter 方式
接下来我们看一下 WebFilter 方式为什么不生效,在根据我的项目源码进行测试的时候,请将需要修改一下自定义的 Filter,将类上的@WebFilter注解打开,@Component注解删除,并且打开 Application 类上的ServletComponentScan
我们这里 debug 的路径和上面的差别不大,重点关注下面ServletContextInitializerBeans的构造方法上面
当我们深入addServletContextInitializerBeans(beanFactory);这一行进去 debug 的时候,会发现我们自定义的 Filter 是在这里面完成初始化的;而前面的使用方式,则是在addAdapterBeans()方法中初始化的,如下图

在getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)的调用中就返回了我们自定义的 Bean,也就是说我们自定义的 Filter 被认为是ServletContextInitializer的类型了
然后我们换个目标,看一下 ReqFilter 在注册的时候是怎样的
关键代码: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
(因为 bean 很多,所以我们可以加上条件断点)

通过断点调试,可以知道我们的自定义 Filter 是通过
WebFilterHandler类扫描注册的, 对这一块管兴趣的可以深入看一下org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor#scanPackage
上面只是声明了 Bean 的注册信息,但是还没有具体的实例化,接下来我们回到前面的进程,看一下 Filter 的实例过程
private <T> List<Entry<String, T>> getOrderedBeansOfType(
ListableBeanFactory beanFactory, Class<T> type, Set<?> excludes) {
Comparator<Entry<String, T>> comparator = (o1,
o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(),
o2.getValue());
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
for (String name : names) {
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
List<Entry<String, T>> beans = new ArrayList<>();
beans.addAll(map.entrySet());
beans.sort(comparator);
return beans;
}
注意我们的 Filter 实例在T bean = beanFactory.getBean(name, type);
通过这种方式获取的 Filter 实例,并不会将 ReqFilter 类上的 Order 注解的值,来更新FilterRegistrationBean的 order 属性,所以这个注解不会生效
最后我们再看一下,通过 WebFilter 的方式,容器类不会存在ReqFilter.class类型的 Bean, 这个与前面的方式不同

III. 小结
本文主要介绍了另外一种 Filter 的使用姿势,将 Filter 当做普通的 Spring Bean 对象进行注册,这种场景下,可以直接使用@Order注解来指定 Filter 的优先级
但是,这种方式下,我们的 Filter 的很多基本属性不太好设置,一个方案是参考 SpringBoot 提供的一些 Fitler 的写法,在 Filter 内部来实现相关逻辑
0. 项目
web 系列博文
- 191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南
- 191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver
- 191010-SpringBoot 系列教程 web 篇之全局异常处理
- 190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置
- 190929-SpringBoot 系列教程 web 篇之重定向
- 190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势
- 190905-SpringBoot 系列教程 web 篇之中文乱码问题解决
- 190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器
- 190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总
- 190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总
- 190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建
- 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建
- 190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建
- 190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明
- 190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析
- 190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建
- 190316-Spring MVC 之基于 xml 配置的 web 应用构建
- 190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid
项目源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 项目:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/210-web-filter
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰 Blog 个人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top

SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇的更多相关文章
- SpringBoot系列教程Web篇之开启GZIP数据压缩
本篇可以归纳在性能调优篇,虽然内容非常简单,但效果可能出乎预料的好: 分享一个真实案例,我们的服务部署在海外,国内访问时访问服务时,响应有点夸张:某些返回数据比较大的接口,耗时在 600ms+上,然而 ...
- SpringBoot系列教程web篇Listener四种注册姿势
java web三要素Filter, Servlet前面分别进行了介绍,接下来我们看一下Listener的相关知识点,本篇博文主要内容为SpringBoot环境下,如何自定义Listener并注册到s ...
- SpringBoot系列教程web篇Servlet 注册的四种姿势
原文: 191122-SpringBoot系列教程web篇Servlet 注册的四种姿势 前面介绍了 java web 三要素中 filter 的使用指南与常见的易错事项,接下来我们来看一下 Serv ...
- SpringBoot系列教程web篇之过滤器Filter使用指南
web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多.基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用) ...
- SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver
关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理: 本篇博文则带来另外一种并不常见的使用方式,通过实 ...
- SpringBoot系列教程web篇之全局异常处理
当我们的后端应用出现异常时,通常会将异常状况包装之后再返回给调用方或者前端,在实际的项目中,不可能对每一个地方都做好异常处理,再优雅的代码也可能抛出异常,那么在 Spring 项目中,可以怎样优雅的处 ...
- SpringBoot系列教程web篇之404、500异常页面配置
接着前面几篇web处理请求的博文,本文将说明,当出现异常的场景下,如404请求url不存在,,403无权,500服务器异常时,我们可以如何处理 原文友链: SpringBoot系列教程web篇之404 ...
- SpringBoot系列教程web篇之重定向
原文地址: SpringBoot系列教程web篇之重定向 前面介绍了spring web篇数据返回的几种常用姿势,当我们在相应一个http请求时,除了直接返回数据之外,还有另一种常见的case -&g ...
- SpringBoot系列教程web篇之如何自定义参数解析器
title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器 banner: /spring-blog/imgs/190831/logo.jpg tags: 请求参数 cat ...
随机推荐
- [淘宝客技术篇008](无需登录)淘宝天猫优惠券JSON接口1
今天,小星给大家分享的是一个非常重要,非常有意义的接口:获取淘宝天猫优惠券的JSON接口. 先上个链接: http://uland.taobao.com/cp/coupon_list?pid=mm_2 ...
- @Qualifier高级应用---按类别批量依赖注入【享学Spring】
每篇一句 罗斯:选秀状元可能有水货,但MVP绝对没有 前言 在上篇文章(讲解@LoadBalanced负载均衡)的末尾,我抛出了一个很重要的问题,建议小伙伴自己深入思考一番:本文主要针对此问题,作出一 ...
- Java如何创建不存在的指定路径的文件?
实际应用中,要在指定位置创建一个文件,但文件及文件之前的目录都不存在,此时可用以下方法进行创建. 以下是主要代码: File f = new File("I:" + File.se ...
- 简单的Socket通信(简单的在线聊天)---winform
注:本博客适合刚开始学习winform程序的初学者,大牛请绕道(跪求大牛指导文中不足) .....10w字废话自动省略,直接开始正题. 首先从最基本的建立winform开始(本项目用的Vs2017) ...
- Centos7上配置nginx的负载均衡
前言 在配置nginx负载均衡前.我们需要明白几个名词的概念 注: 如果不小心忘了tomcat和nginx的启动,关闭命令,可参考写在文章最后的命令 一 重要的概念理解 1 什么是nginx呢? Ng ...
- 微信小程序商城构建全栈应用 Thinkphp5
课程——微信小程序商城构建全栈应用[目录]第1章 前言:不同的时代,不同的Web第2章 环境,工具与准备工作第3章 模块,路由与获取请求参数第4章 构建验证层第5章 REST与RESTFul第6章 A ...
- Nginx 的三大功能
1.HTTP服务器 Nginx是一个HTTP服务器,可以将服务器上的静态文件(如HTML.图片)通过HTTP协议展现给客户端. 2.反向代理服务器 Nginx也是反向代理服务器. 说反向代理之前先说一 ...
- C#加载前生成静态网页
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI ...
- Recovery启动流程(2)---UI界面【转】
Recovery启动流程系列文章把recvoery目录下文件分成小块讲解,最后再以一条主线贯穿所有的内容.这篇文章主要讲解Recovery-UI的相关内容. 我们知道,当我们通过按键或者应用进入rec ...
- 年年有余之java求余的技巧集合
背景 传说里玉皇大帝派龙王马上降雨到共光一带,龙王接到玉皇大帝命令,立马从海上调水,跑去共光施云布雨,但粗心又着急的龙王不小心把海里的鲸鱼随着雨水一起降落在了共光,龙王怕玉皇大帝责怪,灵机一动便声称他 ...