首页 > 编程笔记 > Java笔记 阅读:2

Spring Boot Filter过滤器的用法(非常详细)

过滤器(Filter)是 Web 开发中很实用的一项技术,开发人员可以通过过滤器对 Web 服务管理的资源静态 HTML 文件、静态图片、JSPServlet 等进行拦截,从而实现一些特殊的需求,比如设置 URL 的访问权限、过滤敏感词汇、压缩响应信息等。

过滤器还适用于对用户请求和响应对象进行检查和修改,但是 Filter 本身并不生成请求和响应对象,只是提供过滤功能。

Filter 的完整工作流程如下图所示:


当客户端发出对 Web 资源的请求时,Web 服务器会根据应用程序配置文件设置的过滤规则进行检查,若客户端请求满足过滤规则,则对客户端请求/响应进行拦截:
这就是过滤器的工作原理。

另外,过滤器的生命周期也是由 Web 服务器进行负责的,但是相比真正的 Servlet 又有区别。Filter 的生命周期大致分为以下三个阶段:

@WebFilter实现自定义过滤器

自定义过滤器常用的有两种方式,这里主要讲解通过 @WebFilter 和 @ServletComponentScan 实现过滤器。

定义的过滤器 WebTestFilter.java 必须实现 javax.servlet.Filter 接口,根据自身需求重写 doFilter() 方法。

另外,在启动类上加一个注解 @ServletComponentScan。其中,urlPatterns 是配置过滤器要过滤的 URL,可支持模糊匹配;filterName 只是定义的过滤器名字;@Order(int)注解主要用于多个过滤器时定义执行顺序,值越小越先执行(后面将会详细讲解配置多个Filter的执行顺序)。

参考代码如下:
/**
* 测试单个过滤器
*/
@WebFilter(urlPatterns = "/html/*", filterName = "WebFilter")
@Order(1)
public class WebTestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("WebTestFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }
}
启动项目,访问要过滤的资源(此处以 http://localhost:8080/html/test.html 为例),就可以看到控制台打印的相关内容。如下图所示:

自定义配置类配置过滤器

自定义配置类配置过滤器简而言之就是定义一个过滤器,实现 javax.servlet.Filter 接口,然后将此过滤器以 @Bean 的形式注册到配置类中。

过滤器类的参考代码如下:
/**
* 过滤器,测试案例1
*/
public class RequestTestlFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String filterName = filterConfig.getInitParameter("name");
        System.out.println("过滤器测试案例1,name:" + filterName);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("testl-filter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }
}

过滤器配置类的参考代码如下:
/**
* 配置过滤器
*/
@Configuration
public class WebFilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistTestl() {
        FilterRegistrationBean<Filter> frBean = new FilterRegistrationBean<>();
        RequestTestlFilter requestTestlFilter = new RequestTestlFilter();
        frBean.setFilter(requestTestlFilter);
        frBean.addUrlPatterns("/html/*");
        frBean.addInitParameter("name", "filter-testl");
        frBean.setName("requestTestlFilter");
        frBean.setOrder(4);
        return frBean;
    }
}
此时只要保证 WebFilterConfig.java 被扫描到即可,即配置类上增加 @Configuration 注解,然后启动 Spring Boot 项目即可看到过滤器里定义的内容。此处请求 http://localhost:8080/html/test.html 测试,结果如下图所示:第一行打印的是过滤器名字,第二行是拦截时执行的内容。

配置多个过滤器

对于通过 @WebFilter 定义的多个过滤器,我们真的能通过 @Order(int) 注解实现自定义执行顺序吗?我们可以通过下面的例子测试一下:
/**
* 用于测试 order 定义的过滤器顺序
*/
@WebFilter(urlPatterns = "/html/*", filterName = "AFilter")
@Order(2)
public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("AFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }
}
运行项目,访问 http://localhost:8080/html/test.html,对比 AFilter.java 和 WebTestFilter.java 两个过滤器执行的顺序,结果如下图所示。我们发现先执行的是过滤器 AFilter.java,后执行的是 WebTestFilter.java。


按理说,通过 @Order 注解指定一个 int 值,越小越先执行,但是 AFilter.java 中定义的执行顺序是 order(2),WebTestFilter.java 中定义的执行顺序是 order(1),结果显示先执行的是 AFilter.java 过滤器,说明 @Order(int) 注解和 @WebFilter 注解一起使用的时候并未生效。

我们可以通过源码发现,@WebFilter 修饰的过滤器在加载时并未使用 @Order 注解,而是使用类名来自定义 Filter 的执行顺序,正如当下两个比对的过滤器,执行顺序 AFilter.java > WebTestFilter.java。

从上面的例子我们会发现,如果想以 @WebFilter 方式定义多个过滤器并且限定过滤器执行顺序,就必须限定 Filter 的类名了。以上情况对开发者来说并不友好,所以建议使用配置类注册过滤器的方式配置多个过滤器。

1) 新增一个过滤器,参考代码如下:
/**
* 过滤器,测试案例2
*/
public class RequestTest2Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String filterName = filterConfig.getInitParameter("name");
        System.out.println("过滤器测试案例2,name:" + filterName);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("test2-filter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    }
}

2) 对应修改配置类 WebFilterConfig.java,如下代码所示:
/**
* 配置过滤器,测试过滤器执行顺序
*/
@Configuration
public class WebFilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistTestl() {
        FilterRegistrationBean<Filter> frBean = new FilterRegistrationBean<>();
        RequestTestlFilter requestTestlFilter = new RequestTestlFilter();
        frBean.setFilter(requestTestlFilter);
        frBean.addUrlPatterns("/html/*");
        frBean.addInitParameter("name", "filter-test1");
        frBean.setName("requestTestlFilter");
        frBean.setOrder(2);
        return frBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistTest2() {
        FilterRegistrationBean<Filter> frBean = new FilterRegistrationBean<>();
        RequestTest2Filter requestTest2Filter = new RequestTest2Filter();
        frBean.setFilter(requestTest2Filter);
        // frBean.addUrlPatterns("/html/*");
        // 配置多个过滤规则
        ArrayList<String> urlList = new ArrayList<>();
        urlList.add("/html/test.html");
        urlList.add("/html/login.html");
        frBean.setUrlPatterns(urlList);
        frBean.addInitParameter("name", "filter-test2");
        frBean.setName("requestTest2Filter");
        frBean.setOrder(1);
        return frBean;
    }
}
将两个过滤器 RequestTest1Filter.java 和 RequestTest2Filter.java 注册到配置类 WebFilterConfig.java中,指定 RequestTest1Filter.java 的执行顺序 order为2、RequestTest1Filter.java 的执行顺序 order 为 1。

运行项目,访问 http://localhost:8080/html/test.html,测试结果如下图所示:


正如我们配置的顺序,先执行的是过滤器 RequestTest2Filter.java,后执行的是 RequestTest1Filter.java。

另外,对上面的代码做一些说明:
1) 对于 frBean.addInitParameter("name"," filter-test2"),设置的参数在 Filter 的 init 方法里的 FilterConfig 对象里可以获取,即 filterConfig.getInitParameter("name"),但是初始化时获取的过滤器名字和设定的执行顺序 order 无关,而是根据默认的类加载顺序而来的。

2) 对于 frBean.setUrlPatterns(urls),可以设置多个 URL 匹配规则,setUrlPatterns 接收一个 List<String> 类型的参数。其实现代码在 WebFilterConfig.java 中注册 RequestTest2Filter.java 时已经给出相关示例。

3) 当不设置 setOrder 次序时,过滤器的执行顺序默认是 Bean 的加载顺序。在当前 WebConfig 类中,先加载 RequestTest1Filter.java 过滤器,后加载 RequestTest2Filter.java 过滤器。

相关文章