SpringBoot系列教程web篇之过滤器Filter使用指南
web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多。基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用),在你的日常业务开发中不太可能碰到需要手写Filter的场景
本文将简单介绍写什么是Filter,以及在SpringBoot中使用Filter的一般姿势与常见问题
I. 背景
在正式开始之前,有必要先简单看一下什么是Filter(过滤器),以及这个有什么用
1. Filter说明
Filter,过滤器,属于Servlet规范,并不是Spring独有的。其作用从命名上也可以看出一二,拦截一个请求,做一些业务逻辑操作,然后可以决定请求是否可以继续往下分发,落到其他的Filter或者对应的Servlet
简单描述下一个http请求过来之后,一个Filter的工作流程:
- 首先进入filter,执行相关业务逻辑
 - 若判定通行,则进入Servlet逻辑,Servlet执行完毕之后,又返回Filter,最后在返回给请求方
 - 判定失败,直接返回,不需要将请求发给Servlet
 
插播一句:上面这个过程,和AOP中的
@Around环绕切面的作用差不多
2. 项目搭建
接下来我们搭建一个web应用方便后续的演示,借助SpringBoot搭建一个web应用属于比较简单的活;
创建一个maven项目,pom文件如下
<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,并没有什么特殊的地方,直接实现接口即可,比如下面一个输出请求日志的拦截器
@Slf4j
@WebFilter
public class 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 
@ServletComponentScan
@SpringBootApplication
public class 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注册方式
@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
@Data
@Component
public class DemoBean {
    private long time;
    public DemoBean() {
        time = System.currentTimeMillis();
    }
    public void show() {
        System.out.println("demo bean!!! " + time);
    }
}
@Slf4j
@WebFilter
public class 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方式注册
@Slf4j
@Order(2)
@WebFilter
public class AuthFilter implements Filter, Ordered {
  ...
}
@Slf4j
@Order(1)
@WebFilter
public class ReqFilter implements Filter, Ordered {
  ...
}
@Slf4j
public class OrderFilter implements Filter {
}
@ServletComponentScan
@SpringBootApplication
public class 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注解生效,那么执行的先后顺序应该是
OrderFilter -> ReqFilter -> AuthFilter
如果不是上面的顺序,那么说明@Order注解没有用
@RestController
public class 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的优先级是怎样的呢,我们依然通过debug来看,关键代码路径为: 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的时候指定优先级,如下
@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优先级
 
IV. 其他
web系列博文
- 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篇之过滤器Filter使用指南扩展篇
		
前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filte ...
 - 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篇之自定义异常处理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 ...
 
随机推荐
- C++将模板的声明和定义放置在同一个头文件里
			
1. 一个类: 头文件用于保存类的声明:定义文件保存类的实现. 2. 分离编译模式: 允许在一个编译单元(.cpp文件)中定义函数.类型.类对象等,然后在另一个编译单元中引用它们.编译器处理完所有 ...
 - 使用async/await消除callback hell
			
使用async/await消除callback hell 通过Future回调中再返回Future的方式虽然能避免层层嵌套,但是还是有一层回调,有没有一种方式能够让我们可以像写同步代码那样来执行异步任 ...
 - 硬币游戏1——打表&&记忆化搜索
			
题目 Alice和Bo在玩这样一个游戏,给定 $k$ 个数字 $a_1, a_2,..,a_k$.一开始有 $x$ 枚硬币,Alice和Bob轮流取硬币.每次所取的硬币的枚数一定要在 $k$ 个数当中 ...
 - EasyExcel引入
			
好久没更了,都在有道云上面记录,没时间搬过来. easyexcel是最近项目做优化涉及的一个改善点吧. 简介 导出是后台管理系统的常用功能,当数据量特别大的时候会内存溢出和卡顿页面,曾经自己封装过一个 ...
 - 第八次 Java 作业 重写正方形周长方法
			
# 题目 编写一个应用程序,创建一个矩形类,类中具有长.宽两个成员变量和求周长的方法. 再创建一个矩形类的子类——正方形类,类中定义求面积方法.重写求周长的方法. 在主类中,输入一个正方形边长,创建正 ...
 - UFUN 函数 UF_UI UF_DISP函数( UF_UI_select_with_class_dialog 、UF_DISP_set_highlight)
			
//设置class_dialog选择过滤 static int init_proc(UF_UI_selection_p_t select,void* user_data) { //过滤类别的个数 ; ...
 - Netflix-mantis 实时数据流开发平台
			
mantis 是netflix 开源的已经在netflix 使用了多年的实时流处理平台,目前从官方文档的介绍,在netflix使用场景很多 使用场景 上下文报警 监控netflix 的微服务 异常追踪 ...
 - The Ultimate Guide to handling JWTs on frontend clients (GraphQL)
			
转自:https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/ hasura 团队关于jwt 的实践 JWTs (JSON We ...
 - 退役IV次后做题记录
			
退役IV次后做题记录 我啥都不会了.... AGC023 D 如果所有的楼房都在\(S\)同一边可以直接得出答案. 否则考虑最左最右两边的票数,如果左边>=右边,那么最右边会投给左边,因为就算车 ...
 - uni-app 获取网络状态
			
uni.getNetworkType(OBJECT) 获取网络类型. OBJECT 参数说明 参数名 类型 必填 说明 success Function 是 接口调用成功,返回网络类型 network ...