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()