乐趣区

SpringBoot系列教程web篇之过滤器Filter使用指南

web 三大组件之一 Filter,可以说是很多小伙伴学习 java web 时最早接触的知识点了,然而学得早不代表就用得多。基本上,如果不是让你从 0 到 1 写一个 web 应用(或者说即便从 0 到 1 写一个 web 应用),在你的日常业务开发中不太可能碰到需要手写 Filter 的场景

本文将简单介绍写什么是 Filter,以及在 SpringBoot 中使用 Filter 的一般姿势与常见问题

原文查看: SpringBoot 系列教程 web 篇之过滤器 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 属性,等价于 <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> 标签。

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

退出移动版