1. Servlet Filter(过滤器)

    Servlet Filter 又称 Servlet 过滤器,它是在 Servlet 2.3 规范中定义的,能够对 Servlet 容器传给 Web 资源的 request 对象和 response 对象进行检查和修改。

    Filter 不是 Servlet,不能直接访问,它本身也不能生成 request 对象和 response 对象,它只能为 Web 资源提供以下过滤功能:

        (1) 在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
        (2) 将请求传递到下一个过滤器或目标资源。
        (3) 在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文。

        注意:过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发或重定向到其他的 Web 资源。

    Filter 是 Servlet 规范中最实用的技术,通过它可以对服务器管理的所有 Web 资源(例如 JSP、Servlet、静态 HTML 文件、静态图片等)进行拦截,从而实现一些特殊的功能,例如用户的权限控制、过滤敏感词、设置统一编码格式等。

    1)Filter 接口

        开发过滤器要实现 javax.servlet.Filter 接口,并提供一个公开的不带参的构造方法。在 Filter 接口中,定义了 3 个方法,如下表所示。

方法

描述

void init (FilterConfig filterConfig)

该方法用于初始化过滤器。

void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

当请求的 URL 与过滤器映射的 URL 匹配时,容器会先调用该方法对请求进行拦截。

参数 request 和 response 表示请求和响应对象。

参数 chain 代表当前 Filter 链对象,在该方法内部,调用 chain.doFilter() 方法,才能把请求交付给 Filter 链中的下一个 Filter 或者 Web 资源。

void destroy()

该方法在销毁 Filter 对象之前被调用,用于释放被 Filter 对象占用的资源。

 

    2)Filter 的工作流程

        (1) 客户端请求访问容器内的 Web 资源。
        (2) Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象。
        (3) 请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
        (4) 在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源。
        (5) 目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文。
        (6) 响应信息返回客户端。

    3) Filter 的生命周期

        (1) 初始化阶段

            Servlet 容器负责加载和实例化 Filter。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。

            加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Filter 实例。在 Filter 的生命周期内, init() 方法只执行一次。

        (2) 拦截和过滤阶段

            该阶段是 Filter 生命周期中最重要的阶段。当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。

        (3) 销毁阶段

            Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。

    4) 注册与映射 Filter

        (1) 通过web.xml配置

            在 web.xml 中,通过 <filter> 及其子元素注册 Filter,代码如下。

1                 <filter>
 2                     <filter-name>TestFilter</filter-name>
 3                     <filter-class>net.biancheng.www.MyFilter</filter-class>
 4                     <init-param>
 5                         <param-name>name</param-name>
 6                         <param-value>Tester</param-value>
 7                     </init-param>
 8                     <init-param>
 9                         <param-name>url</param-name>
10                         <param-value>www.test.com</param-value>
11                     </init-param>
12                 </filter>

            以上元素说明如下:

                (1) <filter>  用于注册过滤器
                (2) <filter-name> 是<filter> 元素的子元素, 用于指定过滤器的注册名,该元素的内容不能为空。
                (3) <filter-class> 是<filter> 元素的子元素,用于指定过滤器的完整限定名(包名+类名)。
                (4) <init-param> 是<filter> 元素的子元素,用于为过滤器指定初始化参数,它的子元素 <param-name> 指定参数的名称,<param-value> 指定参数的值。

            在 web.xml 中,通过使用 <filter-mapping> 及其子元素映射 Filter,代码如下。

1                 <filter-mapping>
 2                     <filter-name>TestFilter</filter-name>
 3                     <url-pattern>/login</url-pattern>
 4                     <dispatcher>REQUEST</dispatcher>
 5                     <dispatcher>FORWARD</dispatcher>
 6                 </filter-mapping>
 7                 <filter-mapping>
 8                     <filter-name>TestFilter</filter-name>
 9                     <servlet-name>LoginServlet</servlet-name>
10                 </filter-mapping>

            以上元素说明如下:

                (1) <filter-mapping> 元素用于设置 Filter 负责拦截的资源。
                (2) <filter-name> 是<filter-mapping> 元素的子元素,用于设置 Filter 的注册名,该值必须在 <filter>元素的子元素 <filter-name>  中声明过。
                (3) <url-pattern> 是<filter-mapping> 元素的子元素,用于设置 Filter 拦截的请求路径。
                (4) <servlet-name> 是<filter-mapping> 元素的子元素,用于设置 Filter 拦截的 Servlet 名称。
                (5) <dispatcher> 是<filter-mapping> 元素的子元素,用于指定 Filter 拦截的资源被 Servlet 容器调用的方式,可以是 REQUEST、INCLUDE、FORWARD 和 ERROR 之一,默认 REQUEST。用户可以设置多个 <dispatcher> 子元素指定 Filter 对资源的多种调用方式进行拦截。

            <dispatcher> 元素的取值及其意义:

                (1) REQUEST:当用户直接访问页面时,容器将会调用过滤器。如果目标资源是通过 RequestDispatcher 的 include() 或 forward() 方法访问,则该过滤器就不会被调用。
                (2) INCLUDE:如果目标资源通过 RequestDispatcher 的 include() 方法访问,则该过滤器将被调用。除此之外,该过滤器不会被调用。
                (3) FORWARD:如果目标资源通过 RequestDispatcher 的 forward() 方法访问,则该过滤器将被调用,除此之外,该过滤器不会被调用。
                (4) ERROR:如果目标资源通过声明式异常处理机制访问,则该过滤器将被调用。除此之外,过滤器不会被调用。

        (2) 使用 @WebFilter 注解进行配置

            @WebFilter 注解也可以对过滤器进行配置,容器在部署应用时,会根据其具体属性配置将相应的类部署为过滤器。

            @WebFilter 注解具有下表给出的一些常用属性。以下所有属性均为可选属性,但 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值。

属性名

类型

描述

filterName

String

指定过滤器的 name 属性,等价于 <filter-name>。

urlPatterns

String[]

指定过滤器的 URL 匹配模式。等价于 <url-pattern> 标签。

value

String[]

该属性等价于 urlPatterns 属性,但是两者不能同时使用。

servletNames

String[]

指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中 filterName 属性的取值,或者 web.xml 中 <servlet-name> 的取值。

dispatcherTypes

DispatcherType

指定过滤器拦截的资源被 Servlet 容器调用的方式。具体取值包括: ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。

initParams

WebInitParam[]

指定一组过滤器初始化参数,等价于 <init-param> 标签。

asyncSupported

boolean

声明过滤器是否支持异步操作模式,等价于 <async-supported> 标签。

description

String

指定过滤器的描述信息,等价于 <description> 标签。

displayName

String

指定过滤器的显示名,等价于 <display-name> 标签。

 

2. Servlet FilterChain(过滤器链)

    在 Web 应用中,可以部署多个 Filter,若这些 Filter 都拦截同一目标资源,则它们就组成了一个 Filter 链(也称过滤器链)。过滤器链中的每个过滤器负责特定的操作和任务,客户端的请求在这些过滤器之间传递,直到传递给目标资源。

    1) FilterChain 接口

        javax.servlet 包中提供了一个 FilterChain 接口,该接口由容器实现。容器将其实例对象作为参数传入 Filter 对象的 doFilter() 方法中。Filter 对象可以使用 FilterChain 对象调用链中下一个 Filter 的 doFilter() 方法,若该 Filter 是链中最后一个过滤器,则调用目标资源的 service() 方法。FilterChain 接口中只有一个方法,如下。

        void doFilter(ServletRequest request ,ServletResponse response)     使用该方法可以调用过滤器链中的下一个 Filter 的 doFilter() 方法,若该 Filter 是链中最后一个过滤器,则调用目标资源的 service() 方法。

        在 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法的语句前后增加某些程序代码,就可以在 Servlet 进行响应前后实现某些特殊功能,例如权限控制、过滤敏感词、设置统一编码格式等。

    2) Filter 链的拦截过程

        请求资源时,过滤器链中的过滤器依次对请求进行处理,并将请求传递给下一个过滤器,直到最后将请求传递给目标资源。发送响应信息时,则按照相反的顺序对响应进行处理,直到将响应返回给客户端。

        过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发给其他的目标资源。

            过滤器链中的任何一个 Filter 没有调用 FilterChain.doFilter() 方法,请求都不会到达目标资源。

    3) Filter 链中 Filter 的执行顺序

        通过 web.xml 配置的 Filter 过滤器,执行顺序由 <filter-mapping> 标签的配置顺序决定。<filter-mapping> 靠前,则 Filter 先执行,靠后则后执行。通过修改 <filter-mapping> 的顺序便可以修改 Filter 的执行顺序。

        通过 @WebFilter 注解配置的 Filter 过滤器,无法进行排序,若需要对 Filter 过滤器进行排序,建议使用 web.xml 进行配置。

3. Servlet FilterConfig

    Javax.Servet 包中提供了一个 FilterCofig 接口,它与 ServletConfig 接口相似,用于在过滤器初始化期间向其传递信息。

    FilterConfig 接口由容器实现,容器将它作为参数传入过滤器的 init() 方法中。通过 filterConfig 对象就可以获得 Filter 的初始化参数。

    在 FilterConfig 接口中,定义了 4 个方法,如下表。

方法

描述

String getInitParameter(String name)

根据初始化参数名 name,返回对应的初始化参数值。

Enumeration getInitParameterNames()

返回过滤器的所有初始化参数名的枚举集合。

ServletContext getServletContext()

返回 Servlet 上下文对象的引用。

String getFilterName()

返回过滤器的名称。

示例:

1     // FilterChainOne
  2     package com.example.filter;
  3 
  4     import java.io.IOException;
  5     import java.io.PrintWriter;
  6 
  7     import javax.servlet.Filter;
  8     import javax.servlet.FilterChain;
  9     import javax.servlet.FilterConfig;
 10     import javax.servlet.ServletException;
 11     import javax.servlet.ServletRequest;
 12     import javax.servlet.ServletResponse;
 13 
 14     import javax.servlet.DispatcherType;
 15     import javax.servlet.annotation.WebFilter;
 16     import javax.servlet.annotation.WebInitParam;
 17 
 18     @WebFilter(filterName = "FilterChainOne",
 19                 dispatcherTypes = {
 20                     DispatcherType.REQUEST,
 21                     DispatcherType.FORWARD,
 22                 },
 23                 urlPatterns = {"/filter"},
 24                 initParams = {
 25                     @WebInitParam(name = "charset", value = "utf-8"),
 26                 })
 27     public class FilterChainOne implements Filter {
 28         private String strFilterName;
 29         private String strCharset;
 30 
 31         public void init(FilterConfig config) throws ServletException {
 32             strFilterName = config.getFilterName();
 33             strCharset = config.getInitParameter("charset");
 34 
 35             System.out.println("FilterChainOne -> init() : filterName = " + strFilterName + ", charset = " + strCharset);
 36         }
 37 
 38         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 39                 throws IOException, ServletException {
 40 
 41             response.setContentType("text/html;charset=" + strCharset);
 42             request.setCharacterEncoding(strCharset);
 43             response.setCharacterEncoding(strCharset);
 44 
 45             PrintWriter writer = response.getWriter();
 46             writer.write("FilterChainOne -> before chain.doFilter()<br/>");
 47             chain.doFilter(request, response);
 48             writer.write("FilterChainOne -> after chain.doFilter()<br/>");
 49 
 50             System.out.println("FilterChainOne -> doFilter()");
 51         }
 52 
 53         public void destroy() {
 54             System.out.println("FilterChainOne -> destroy() : " + strFilterName);
 55         }
 56     }
 57 
 58     // FilterChainTwo
 59     package com.example.filter;
 60 
 61     import java.io.IOException;
 62     import java.io.PrintWriter;
 63 
 64     import javax.servlet.Filter;
 65     import javax.servlet.FilterChain;
 66     import javax.servlet.FilterConfig;
 67     import javax.servlet.ServletException;
 68     import javax.servlet.ServletRequest;
 69     import javax.servlet.ServletResponse;
 70 
 71     import javax.servlet.DispatcherType;
 72     import javax.servlet.annotation.WebFilter;
 73     import javax.servlet.annotation.WebInitParam;
 74 
 75     @WebFilter(filterName = "FilterChainTwo",
 76             dispatcherTypes = {
 77                     DispatcherType.REQUEST,
 78                     DispatcherType.FORWARD,
 79             },
 80             urlPatterns = {"/filter"})
 81     public class FilterChainTwo implements Filter {
 82 
 83         public FilterChainTwo() {
 84 
 85         }
 86 
 87         public void init(FilterConfig config) throws ServletException {
 88 
 89         }
 90 
 91         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 92                 throws IOException, ServletException {
 93 
 94             response.setContentType("text/html;charset=UTF-8");
 95 
 96             PrintWriter writer = response.getWriter();
 97             writer.write("FilterChainTwo -> before chain.doFilter()<br/>");
 98             chain.doFilter(request, response);
 99             writer.write("FilterChainTwo -> after chain.doFilter()<br/>");
100         }
101 
102         public void destroy() {
103 
104         }
105     }
106 
107     // FilterChainThree
108     package com.example.filter;
109 
110     import java.io.IOException;
111     import java.io.PrintWriter;
112 
113     import javax.servlet.Filter;
114     import javax.servlet.FilterChain;
115     import javax.servlet.FilterConfig;
116     import javax.servlet.ServletException;
117     import javax.servlet.ServletRequest;
118     import javax.servlet.ServletResponse;
119 
120     import javax.servlet.DispatcherType;
121     import javax.servlet.annotation.WebFilter;
122     import javax.servlet.annotation.WebInitParam;
123 
124     @WebFilter(filterName = "FilterChainThree",
125             dispatcherTypes = {
126                     DispatcherType.REQUEST,
127                     DispatcherType.FORWARD,
128             },
129             urlPatterns = {"/filter"})
130     public class FilterChainThree implements Filter {
131 
132         public FilterChainThree() {
133         }
134 
135         public void init(FilterConfig fConfig) throws ServletException {
136 
137         }
138 
139         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
140                 throws IOException, ServletException {
141 
142             response.setContentType("text/html;charset=UTF-8");
143 
144             PrintWriter writer = response.getWriter();
145             writer.write("FilterChainThree -> before chain.doFilter()<br/>");
146             chain.doFilter(request, response);
147             writer.write("FilterChainThree -> after chain.doFilter()<br/>");
148         }
149 
150         public void destroy() {
151 
152         }
153     }
154 
155     // FilterServlet  
156     package com.example.filter;
157 
158     import java.io.IOException;
159     import java.io.PrintWriter;
160 
161     import javax.servlet.ServletException;
162     import javax.servlet.annotation.WebServlet;
163     import javax.servlet.http.HttpServlet;
164     import javax.servlet.http.HttpServletRequest;
165     import javax.servlet.http.HttpServletResponse;
166 
167     @WebServlet(name = "FilterServlet",
168             urlPatterns = {"/filter"})
169     public class FilterServlet extends HttpServlet {
170 
171         protected void doGet(HttpServletRequest request, HttpServletResponse response)
172                 throws ServletException, IOException {
173             response.setContentType("text/html;charset=UTF-8");
174             PrintWriter writer = response.getWriter();
175             writer.write("<h3>FilterServlet -> Got it!</h3>");
176         }
177 
178         protected void doPost(HttpServletRequest request, HttpServletResponse response)
179                 throws ServletException, IOException {
180 
181         }
182 
183     }

 

访问 http://localhost:9090/filter (本文测试端口 9090,Tomcat默认端口 8080),Web页面输出:

FilterChainTwo -> before chain.doFilter()
    FilterChainThree -> before chain.doFilter()
    FilterChainOne -> before chain.doFilter()
    FilterServlet -> Got it!
    FilterChainOne -> after chain.doFilter()
    FilterChainThree -> after chain.doFilter()
    FilterChainTwo -> after chain.doFilter()