[toc]
过滤器
可以执行 对请求进行预处理、不同组件公共部分统一处理、最终响应进行修改 等操作。
1. 简介
- 功能:对容器传给Web组件的ServletRequest和ServletResponse对象进行检查和修改:
- 在调用Web组件前检查ServletRequest对象,修改请求头和请求正文,或对请求预处理
- 在调用Web组件后检查ServletResponse对象,修改响应头和响应正文
- Web组件可以是Servlet、JSP或HTML。
- 流程:客户Web端$\iff$Servlet容器$\iff$过滤器$\iff$Web组件
- 特点:
- 可以检查ServletRequest和ServletResponse对象,并用ServletRequestWrapper和SErvletResponseWrapper包装类来修改
- 可以在web.xml中为过滤器映射特定的URL,当请求此URL时就先触发过滤器
- 多个过滤器可以串联,协同为Web组件过滤请求对象和响应对象
2. 创建过滤器
-
自定义过滤器类要实现javax.servlet.Filter接口,该接口含有3个必须实现方法:
- init(FilterConfig):初始化;Web应用启动时,容器先创建包含了过滤器配置信息的FilterConfig对象,然后创建Filter对象,接着调用init方法;该方法中可通过config参数来读取web.xml中为过滤器配置的初始化参数
- doFilter(ServletRequest, ServletResponse, FilterChain):完成实际过滤操作,请求URL与过滤器URL匹配时,容器调用过滤器的doFilter方法;FilterChain用于访问后续过滤器或Web组件
- destroy():容器销毁过滤器对象前调用,可在其中释放资源
-
过滤器由容器创建,其生命周期:
- 初始化阶段:Web应用启动,容器加载过滤器类,创建过滤器配置对象FilterConfig和过滤器对象,调用过滤器对象的init方法
- 运行时阶段:请求URL与过滤器URL匹配时,容器调用过滤器doFilter方法
- 销毁阶段:Web应用终止时,容器先调用过滤器对象的destroy方法,然后销毁对象
-
NoteFilter类例子,为NoteServlet过滤,若访问IP或用户名处于黑名单就直接拒绝访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61public class NoteFilter implements Filter {
private FilterConfig config = null;
private String blackList=null;
private String ipblock=null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
// 读取ipblock初始化参数
ipblock=config.getInitParameter("ipblock");
// 读取blacklist初始化参数
blackList=config.getInitParameter("blacklist");
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 如果ip和用户名已被拉入黑名单,就直接返回拒绝信息,不再调用后续组件
if(!checkRemoteIP(request,response))return;
if(!checkUsername(request,response))return;
// 把请求转发给后续组件
chain.doFilter(request, response);
}
private boolean checkRemoteIP(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// 读取客户IP
String addr=request.getRemoteAddr();
if(addr.indexOf(ipblock)==0){
response.setContentType("text/html;charset=GB2312");
PrintWriter out = response.getWriter();
out.println("<h1>拒绝访问</h1>");
out.flush();
return false;
}else{
return true;
}
}
private boolean checkUsername(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
String username =((HttpServletRequest) request).getParameter("username");
if(username!=null)
username=new String(username.getBytes("ISO-8859-1"),"GB2312");
if (username!=null && username.indexOf(blackList) != -1 ) {
response.setContentType("text/html;charset=GB2312");
PrintWriter out = response.getWriter();
out.println("<h1>对不起,"+username + ",你无权访问 </h1>");
out.flush();
return false;
}else{
return true;
}
}
public void destroy() {
config = null;
}
}初始化方法中,
config.getInitParameter("ipblock")
这一方法,从web.xml中读取初始化参数ipblock。
3. 发布过滤器
两种方法:
- web.xml中通过<filter>元素和<filter-mapping>元素配置
- 过滤器类中用@WebFilter标注来配置
3.1 web.xml配置过滤器
1 | <filter> |
若url-pattern为/*,则为所有组件过滤。
3.2 @WebFilter标注配置
-
常用属性:
- filterName:String,指定过滤器名字,相当于<filter-name>
- urlPatterns:String[],一组匹配模式,<url-pattern>
- value:String[],等价于urlPatterns属性,但不可同时使用
- initParams:WebInitParam[],一组过滤器初始化参数,<init-param>
- asyncSupported:boolean,是否支持异步处理模式,<async-supported>
- Description:String,<description>
- displayName:String,显示名,需配合其他工具,<display-name>
- dispatcherTypes:DispatcherType,指定过滤器调用模式,可选ASYNC, ERROR, FORWARD, INCLUDE, REQUEST
-
如上例中的NoteFilter类,用如此配置:
1
2
3
4
5
6
7
8(
filterName = "NoteFilter",
urlPatterns = "/note",
initParams = {
"ipblock", value = "221.45"), (name =
"blacklist", value = "捣蛋鬼") (name =
}
) -
dispatcherTypes属性:指定调用过滤器模式,取值:
- DispatcherType.ASYNC:待过滤目标被异步访问时,容器先调用过滤器
- DispatcherType.ERROR:待过滤目标是通过声明式异常处理机制被访问时,容器先调用过滤器
- DispatcherType.FORWARD:当待过滤目标是通过RequestDispatcher.forward()/请求转发被调用时,容器先调用过滤器
- DispatcherType.INCLUDE:当待过滤目标是通过RequestDispatcher.include()/请求包含被调用时,容器先调用过滤器
- DispatcherType.REQUEST:客户端直接请求访问待过滤的目标时,容器先调用过滤器
- 本属性允许取多个值,如
dispatcherTypes=DispatcherType.FORWARD
,或``dispatcherTypes={DispatcherType.REQUEST, DispatcherType.INCLUDE}`
3.3 发布步骤
- 编译Filter对象和目标组件Servlet,编译时需要在classpath下加入servlet-api.jar,位于<CATALINA_HOME>/lib下;编译结果class位于<CATALINA_HOME>/webapps/webapp/WEB-INF/classes/mypack下
- web.xml中配置,或用标注配置(配置xml时,若有中文需指定编码
<?xml version="1.0" encoding="GB2312"?>
),且xml中所有过滤器先于Servlet配置
4. 串联过滤器
- 多个过滤器串联工作,容器根据其在web.xml中定义的先后顺序依次调用doFilter方法
- 流程举例:两个过滤器:客户请求->容器->过滤器1的chain.doFilter前的代码->过滤器1执行chain.doFilter->过滤器2的chain.doFilter前的代码->过滤器2执行chain.doFilter->执行Servlet的service方法->过滤器2的chain.doFilter后的代码->过滤器1的chain.doFilter后的代码->容器返回给客户
- 以下举例,过滤器修改response中的内容,本例中是更改特定字符串;实现本功能需要3个类:
- ReplaceTextStream类:ServletOutputStream的包装类,对容器创建的ServletOutputStream对象进行包装
- ReplaceTextWrapper类:HttpServletResponse的包装类,对容器创建的HttpServletResponse对象进行包装
- ReplaceTextFilter类:过滤器类,对响应结果进行字符串替换
4.1 包装设计模式简介
ReplaceTextStream类和ReplaceTextWrapper类都采用了包装设计模式(装饰器设计模式)。
包装设计模式特点:
- 对客户透明,动态地给对象附加更多功能,或修改对象部分功能
- 若A是B的包装类,则A与B有同样的接口,且A有B的实例,A借助B的实例来实现接口
容器已提供了HttpServletResponse的包装类HttpServletResponseWrapper,它继承了ServletResponseWrapper,内含一个HttpServletResponse对象;这个包装类并没有修改被包装的对象的功能,主要作用是提供本包装类的默认实现,用户自定义HttpServletResponse的包装类时只要继承这个已有包装类,并修改方法,就能修改被包装对象的功能。
4.2 ServletOutputStream的包装类
本例中ReplaceTextStream类是ServletOutputStream的包装类,它包装了容器提供的ServletOutputStream对象,并覆盖了它的一些方法。在构造包装类实例时,要先把容器提供的ServletOutputStream对象传入。
1 | public class ReplaceTextStream extends ServletOutputStream { |
4.3 HttpServletResponse的包装类
本例中的ReplaceTextWrapper是HttpServletResponseWrapper的子类,包装了容器提供的HttpServletResponse对象,覆盖了父类的getOutputStream方法。
ReplaceTextWrapper在构造方法中创建了一个ReplaceTextStream对象,getOutputStream方法返回这个对象,而非返回容器提供的ServletOutputStream。
1 | public class ReplaceTextWrapper extends HttpServletResponseWrapper { |
4.4 创建过滤器
本例中的ReplaceTextFilter过滤器,在init初始化中调用config.geetInitParameter(“”)方法读取旧串和新串。
随后doFilter中,创建一个ReplaceTextWrapper对象,调用chain.doFilter(req, myWrapperResp),将ReplaceTextWrapper对象传给后续过滤器或Servlet,本例中是NoteServlet。如此,当NoteServlet调用response.getOutputStream时,获取到的并非容器提供的ServletOutputStream对象,而是包装对象ReplaceTextStream对象。
1 | public class ReplaceTextFilter implements Filter { |
补:本例修改response,若需修改request,则要创建HttpServletRequest的包装类,继承HttpServletRequestWrapper类,覆盖部分方法。过滤器把这个包装类传给后续Web组件。
4.5 本例过滤器的工作时序
若未安装过滤器,则NoteServlet的service方法,从参数中获得容器提供的HttpServletResponse对象,则response.getOutputStream()得到容器提供的ServletOutputStream对象。通过这个流输出数据,无法修改内容。
若安装过滤器,则NoteServlet得到的是ReplaceTextWrapper对象(ServletResponse的包装类),此时response.getOutputStream()得到ReplaceTextStream对象(ServletOutputStream的包装类)。通过这个输出流输出,就能先将输出内容存入缓存,替换后再输出到ServletOutputStream。
即,实际的替换工作是在输出流的包装类中进行的;不过为了使工作Servlet能够将内容输出到该流类,就需要包装流类、包装response类。
4.6 发布应用
-
编译各个类 [参考3.3 发布步骤](3.3 发布步骤)
-
web.xml中配置过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<filter>
<filter-name>ReplaceTextFilter</filter-name>
<filter-class>mypack.ReplaceTextFilter</filter-class>
<init-param>
<param-name>search</param-name>
<param-value>暴力</param-value>
</init-param>
<init-param>
<param-name>replace</param-name>
<param-value>和平</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ReplaceTextFilter</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>本例中配置为将“暴力”替换为“和平”。
5. 异步处理过滤器
处理器也支持异步处理机制,此时需要在配置中开启asyncSupported。
过滤器AsyncFilter
1 | ( |
AsyncServlet
1 | "AsyncServlet", (name= |
为使过滤器能够与Servlet共享同一个表示异步处理上下文的AsyncContext对象,可如下创建AsyncContext对象:
1. 在过滤器的doFilter方法中,通过req.startAsync方法,创建AsyncContext对象
2. 在Servlet的service方法中,通过req.getAsyncContext方法获取之前创建的对象
3. 由Servlet的内部类MyTask调用AsyncContext对象的complete方法,提交任务
另,若过滤器使用异步,则串联的过滤器和Servlet都要异步。