Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

Filter(过滤器)的使用

1、Filter简介

Filter称之为过滤器。我们生活中的过滤器有过滤网、净水器、空气净化器等。而Web中过滤器是用来对Web服务器管理的Web资源进行拦截(如JSP, Servlet, 静态图片文件或静态html文件等),从而完成一些特殊的功能。比如:实现URL级别的权限访问控制(最常用)、字符编码、登录限制、过滤敏感词汇、文件压缩,跨域设置等一些高级功能。

在Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,如下所示:

image

上面图片的理解就是:当一个请求过来时,首先会被过滤器拦截下来,若满足条件,则进入下一步,执行完毕之后,又返回Filter,最后再返回给客户端。若不满足条件,则直接返回。

2、Filter开发步骤

Filter开发步骤很简单,分为两个步骤:

  1. 编写Java类实现Filter接口,并实现其doFilter方法(也以重写init方法与destroy方法)。
  2. 在web.xml 文件中使用<filter><filter-mapping>元素配置filter,并设置它所能拦截的资源。当然我们也可以通过注解来实现。

下面举个简单的例子来说明下:字符编码拦截。我还记的在之前的博客中,如果要把含有中文的数据输出到网页中而不出现乱码的话,就必须在代码中下面这些信息。

request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");

如果页面不多的话还好,如果页面多了的话,就要写很多重复。而用过滤器只需写一遍即可。

使用Filter代码示例:

package com.thr;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author tanghaorong
 * @date 2020-05-02
 * @desc  创建一个实现Filter的实现类
 */
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter初始化,只初始化一次...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        //对request和response进行一些预处理
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        System.out.println("拦截前执行...");
        filterChain.doFilter(request,response);
        System.out.println("拦截后执行...");
    }

    @Override
    public void destroy() {
        System.out.println("Filter销毁...");
    }
}

在web. xml中配置过滤器:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置过滤器-->
       <filter>
           <filter-name>MyFilter</filter-name>
           <filter-class>com.thr.MyFilter</filter-class>
       </filter>

       <!--映射过滤器-->
       <filter-mapping>
           <filter-name>MyFilter</filter-name>
           <!--/* :表示拦截所有的请求 -->
           <url-pattern>/*</url-pattern>
       </filter-mapping>

</web-app>

拦截的规则和Servlet的匹配规则一样,它们的拦截规则如下:

  • 以指定资源匹配。例如:/index.jsp、/login
  • 以目录匹配。例如:/servlet/* 、 /abc/def
  • 以后缀名匹配,例如:*.jsp 、 *.do
  • 通配符,拦截所有web资源:/*

注意的点也是一样的:/abc/*.do、/*.do、abc*.do 这些都是非法的,这样的拦截规则是无效的。

我们也可以为一个过滤器配置多个映射,也就是一个Filter对应多个。但是这样设计好像没什么用,因为只要有一个URL匹配就会被拦截。

上面是对于单个Filter配置多个mapping的情况,而后面还有多个Filter对应多个mapping,这种多个Filter组合起来就称之为一个Filter链,此时的Filter的执行顺序是不确定的。因为这个地方难以理解,所以后面会详细介绍。

3、Filter的注解配置

当然我们也可以不在web.xml中配置,可以使用注解——@WebFilter。@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解包含的所有属性如下:

属性名 类型 描述
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>标签。
smallIcon String 此Filter的小图标。
largeIcon String 此Filter的大图标。

使用@WebFilter的一个简单举例,还是用上面MyFilter过滤器。不过在测试的发现了一个问题,就是使用了注解配置Filter,但是继续在web.xml文件中配置该Filter(配置信息一样),好像是不会报错的。这个我也不知道是怎么回事,每种方式我都去试了一下,结果不会出现问题,可能过滤器是支持这种方式的吧,但是Servlet就不行会报错。(如果有大神知道可以多多指出,毕竟我还是个菜鸟)

/**
 * @author tanghaorong
 * @date 2020-05-03
 * @desc  使用注解配置Filter
 */
@WebFilter(filterName = "myFilter",value = "/*")
public class MyFilter implements Filter {

    code...

}

上面使用注解和web.xml同时配置不报错的原因是:服务器在启动时首先是会加载web.xml的文件,Filter的映射据<filter-mapping>的顺序来映射的,由于上面的代码使用注解和web.xml同时配置了同一个Filter,相当于配置两次<filter><filter-mapping>。故这个Filter会初始化两次,由于先加载的web.xml文件,所以当@WebFilter设置的过滤器被初始化时,它得到的字符串为空。

4、Filter的生命周期

在Filter接口中定义了三个方法,这三个都是与Filter的生命相关的方法,我们看一下源码:

package javax.servlet;

import java.io.IOException;

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

对于这三个方法的介绍:

  1. init(FilterConfig var1):表示Filter对象的初始化方法,在Filter对象创建时执行(只执行一次),并且传入一个FilterConfig类型的参数,该参数封装了Filter的<init-param>初始化参数。
  2. doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3):表示Filter执行过滤的核心方法,如果某资源在已经被配置到这个Filter进行过滤的话,那么每次访问这个资源都会执行doFilter方法,注意 这里的request和response没有http需要强制转换。
  3. destory():表示Filter销毁方法,当Filter对象销毁时执行该方法(仅执行一次)。

5、多个过滤器的执行顺序

如果在一个Web应用中,编写了多个Filter,那么这些Filter组合起来称之为一个Filter链。此时的Web服务器根据Filter在web.xml文件中的<filter-mapping>定义顺序执行。如果是注解的话则根据类名先后顺序执行。如果web.xml和注解混合使用,则先加载web.xml然后注解。当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则Web服务器会检查FilterChain对象中是否还有Filter,如果还有,则调用第2个Filter,依次类推,直到没有可以调用目标资源。

image

举个例子好好理解一下。分别创建A、B、C、D 四个Filter。其中A和D过滤器用注解配置,B和C过滤器用web.xml配置。

AFilter的代码示例如下:(因为DFilter代码大致相同,所以我就贴一个,就类名和打印时的名称不一样罢了。如果使用web.xml配置就把注解注释掉即可)

/**
 * @author tanghaorong
 * @date 2020-05-03
 * @desc 使用注解配置AFilter
 */
@WebFilter(filterName = "AFilter",value = "/*")
public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始过滤器名称:AFilter");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("AFilter拦截前执行...");
        filterChain.doFilter(request,response);
        System.out.println("AFilter拦截后执行...");
    }

    @Override
    public void destroy() {
        System.out.println("AFilter销毁...");
    }
}

BFilter和CFilter在web.xml的配置如下:(注意:执行顺序跟<filter>的顺序无关。而是根据<filter-mapping>的顺序决定执行顺序)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

        <!--配置过滤器-->
        <filter>
            <filter-name>CFilter</filter-name>
            <filter-class>com.thr.CFilter</filter-class>
        </filter>
        <filter>
            <filter-name>BFilter</filter-name>
            <filter-class>com.thr.BFilter</filter-class>
        </filter>

        <!--映射过滤器-->
        <!--注意:这里CFilter在BFilter之前-->
        <filter-mapping>
           <filter-name>CFilter</filter-name>
           <url-pattern>/*</url-pattern>
        </filter-mapping>

        <filter-mapping>
            <filter-name>BFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

</web-app>

运行后的结果如下:

①、这是初始化的顺序截图:

image

每次运行的结果都是这个,不知道过滤器初始化创建的顺序根据什么来的执行的?不过这不是重点,重点在下面。

②、这是过滤器运行顺序截图:

image

可以看到,过滤器的执行结果符合我们的预期。CFilter和BFilter是在web.xml中声明的,且CFilter的定义在BFilter之前,所以CFilter在BFilter之前执行。而注解则是根据类来执行的。并且可以发现每当过滤器调用filterChain.doFilter(request,response)之后,检查到后面还有过滤器没有执行,则继续执行后面的过滤器,当检查到没有过滤器后,再依次执行过滤器后面的操作。

③、这是过滤器销毁顺序截图:

image

销毁的执行顺序和初始化是一样的。

我后面又试了试很多种方法,注解和web.xml混合配置,全注解配置,全web.xml配置。

可以的出一个结论:web.xml方式是根据mapping中先后顺序执行。而注解方式则是根据类名先后顺序排序的。但是Filter初始化和销毁时的顺序我也不知道是根据什么排的序,无论这么测试输出的格式都是一样的。

6、FilterConfig对象的使用

还记得在Servlet中有个ServletConfig,它代表的是当前Servlet的一些初始化参数。所以FilterConfig也是一样的,当我们在配置Filter的时候,使用<init-param>为Filter配置一些初始化参数后,在Filter对象被创建并且调用其init方法时,会把封装了Filter初始化参数的FilterConfig对象传递进来。因此通过FilterConfig对象的方法就可获取初始化配置信息。方法如下:

  1. String getFilterName():返回Filter的名称。
  2. String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
  3. Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
  4. ServletContext getServletContext():返回Servlet上下文对象的引用。

使用FilterConfig获取Filter的初始化配置信息:

web.xml中配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置过滤器-->
       <filter>
           <filter-name>MyFilter</filter-name>
           <filter-class>com.thr.MyFilter</filter-class>
           <!--配置初始化信息-->
           <init-param>
               <param-name>name</param-name>
               <param-value>tanghaorong</param-value>
           </init-param>
           <init-param>
               <param-name>password</param-name>
               <param-value>123456</param-value>
           </init-param>
       </filter>

       <!--映射过滤器-->
       <filter-mapping>
           <filter-name>MyFilter</filter-name>
           <!--/* :表示拦截所有的请求 -->
           <url-pattern>/*</url-pattern>
       </filter-mapping>

</web-app>

FilterConfig中的代码:

package com.thr;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.Enumeration;

/**
 * @author tanghaorong
 * @date 2020-05-03
 * @desc  FilterConfig获取Filter初始化配置信息
 */
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter初始化,只初始化一次...");

        //获取过滤器的名字
        String filterName = filterConfig.getFilterName();
        //获取在web.xml文件中配置的初始化参数
        String initParam1 = filterConfig.getInitParameter("name");
        String initParam2 = filterConfig.getInitParameter("password");

        System.out.println(filterName);
        System.out.println(initParam1);
        System.out.println(initParam2);

        //返回过滤器的所有初始化参数的名字的枚举集合。
        Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String name = (String) initParameterNames.nextElement();
            String value = filterConfig.getInitParameter(name);
            System.out.println(name + "=" + value );
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("拦截前执行...");
        filterChain.doFilter(request,response);
        System.out.println("拦截后执行...");
    }

    @Override
    public void destroy() {
        System.out.println("Filter销毁...");
    }
}

使用注解获取:

package com.thr;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

/**
 * @author tanghaorong
 * @date 2020-05-03
 * @desc 使用注解配置Filter初始化参数,并且用FilterConfig获取
 */
@WebFilter(filterName = "myFilter",value = "/*",initParams = {
        @WebInitParam(name = "name", value = "tanghaorong"),/*这里配置初始化的参数*/
        @WebInitParam(name = "password", value = "123456")/*相当于<init-param>*/
})
public class MyFilter implements Filter {

    //此段代码和上面是一样的
    code...

}
posted @ 2020-04-30 22:09  唐浩荣  阅读(1620)  评论(0编辑  收藏  举报