[toc]

 

过滤器

可以执行 对请求进行预处理、不同组件公共部分统一处理、最终响应进行修改 等操作。

 

1. 简介

  1. 功能:对容器传给Web组件的ServletRequest和ServletResponse对象进行检查和修改:
    1. 在调用Web组件前检查ServletRequest对象,修改请求头和请求正文,或对请求预处理
    2. 在调用Web组件后检查ServletResponse对象,修改响应头和响应正文
  2. Web组件可以是Servlet、JSP或HTML。
  3. 流程:客户Web端$\iff$Servlet容器$\iff$过滤器$\iff$Web组件
  4. 特点:
    1. 可以检查ServletRequest和ServletResponse对象,并用ServletRequestWrapper和SErvletResponseWrapper包装类来修改
    2. 可以在web.xml中为过滤器映射特定的URL,当请求此URL时就先触发过滤器
    3. 多个过滤器可以串联,协同为Web组件过滤请求对象和响应对象

 

2. 创建过滤器

  1. 自定义过滤器类要实现javax.servlet.Filter接口,该接口含有3个必须实现方法:

    1. init(FilterConfig):初始化;Web应用启动时,容器先创建包含了过滤器配置信息的FilterConfig对象,然后创建Filter对象,接着调用init方法;该方法中可通过config参数来读取web.xml中为过滤器配置的初始化参数
    2. doFilter(ServletRequest, ServletResponse, FilterChain):完成实际过滤操作,请求URL与过滤器URL匹配时,容器调用过滤器的doFilter方法;FilterChain用于访问后续过滤器或Web组件
    3. destroy():容器销毁过滤器对象前调用,可在其中释放资源
  2. 过滤器由容器创建,其生命周期:

    1. 初始化阶段:Web应用启动,容器加载过滤器类,创建过滤器配置对象FilterConfig和过滤器对象,调用过滤器对象的init方法
    2. 运行时阶段:请求URL与过滤器URL匹配时,容器调用过滤器doFilter方法
    3. 销毁阶段:Web应用终止时,容器先调用过滤器对象的destroy方法,然后销毁对象
  3. 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
    61
    public 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. 发布过滤器

两种方法:

  1. web.xml中通过<filter>元素和<filter-mapping>元素配置
  2. 过滤器类中用@WebFilter标注来配置

3.1 web.xml配置过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<filter> 
<filter-name>NoteFilter</filter-name>
<filter-class>mypack.NoteFilter</filter-class>

<init-param>
<param-name>ipblock</param-name>
<param-value>221.45</param-value>
</init-param>

<init-param>
<param-name>blacklist</param-name>
<param-value>捣蛋鬼</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>NoteFilter</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>

若url-pattern为/*,则为所有组件过滤。

3.2 @WebFilter标注配置

  1. 常用属性:

    1. filterName:String,指定过滤器名字,相当于<filter-name>
    2. urlPatterns:String[],一组匹配模式,<url-pattern>
    3. value:String[],等价于urlPatterns属性,但不可同时使用
    4. initParams:WebInitParam[],一组过滤器初始化参数,<init-param>
    5. asyncSupported:boolean,是否支持异步处理模式,<async-supported>
    6. Description:String,<description>
    7. displayName:String,显示名,需配合其他工具,<display-name>
    8. dispatcherTypes:DispatcherType,指定过滤器调用模式,可选ASYNC, ERROR, FORWARD, INCLUDE, REQUEST
  2. 如上例中的NoteFilter类,用如此配置:

    1
    2
    3
    4
    5
    6
    7
    8
    @WebFilter(
    filterName = "NoteFilter",
    urlPatterns = "/note",
    initParams = {
    @WebInitParam(name = "ipblock", value = "221.45"),
    @WebInitParam(name = "blacklist", value = "捣蛋鬼")
    }
    )
  3. dispatcherTypes属性:指定调用过滤器模式,取值:

    1. DispatcherType.ASYNC:待过滤目标被异步访问时,容器先调用过滤器
    2. DispatcherType.ERROR:待过滤目标是通过声明式异常处理机制被访问时,容器先调用过滤器
    3. DispatcherType.FORWARD:当待过滤目标是通过RequestDispatcher.forward()/请求转发被调用时,容器先调用过滤器
    4. DispatcherType.INCLUDE:当待过滤目标是通过RequestDispatcher.include()/请求包含被调用时,容器先调用过滤器
    5. DispatcherType.REQUEST:客户端直接请求访问待过滤的目标时,容器先调用过滤器
    6. 本属性允许取多个值,如dispatcherTypes=DispatcherType.FORWARD,或``dispatcherTypes={DispatcherType.REQUEST, DispatcherType.INCLUDE}`

3.3 发布步骤

  1. 编译Filter对象和目标组件Servlet,编译时需要在classpath下加入servlet-api.jar,位于<CATALINA_HOME>/lib下;编译结果class位于<CATALINA_HOME>/webapps/webapp/WEB-INF/classes/mypack下
  2. web.xml中配置,或用标注配置(配置xml时,若有中文需指定编码 <?xml version="1.0" encoding="GB2312"?>),且xml中所有过滤器先于Servlet配置

 

4. 串联过滤器

  1. 多个过滤器串联工作,容器根据其在web.xml中定义的先后顺序依次调用doFilter方法
  2. 流程举例:两个过滤器:客户请求->容器->过滤器1的chain.doFilter前的代码->过滤器1执行chain.doFilter->过滤器2的chain.doFilter前的代码->过滤器2执行chain.doFilter->执行Servlet的service方法->过滤器2的chain.doFilter后的代码->过滤器1的chain.doFilter后的代码->容器返回给客户
  3. 以下举例,过滤器修改response中的内容,本例中是更改特定字符串;实现本功能需要3个类:
    1. ReplaceTextStream类:ServletOutputStream的包装类,对容器创建的ServletOutputStream对象进行包装
    2. ReplaceTextWrapper类:HttpServletResponse的包装类,对容器创建的HttpServletResponse对象进行包装
    3. ReplaceTextFilter类:过滤器类,对响应结果进行字符串替换

 

4.1 包装设计模式简介

ReplaceTextStream类和ReplaceTextWrapper类都采用了包装设计模式(装饰器设计模式)。

包装设计模式特点:

  1. 对客户透明,动态地给对象附加更多功能,或修改对象部分功能
  2. 若A是B的包装类,则A与B有同样的接口,且A有B的实例,A借助B的实例来实现接口

容器已提供了HttpServletResponse的包装类HttpServletResponseWrapper,它继承了ServletResponseWrapper,内含一个HttpServletResponse对象;这个包装类并没有修改被包装的对象的功能,主要作用是提供本包装类的默认实现,用户自定义HttpServletResponse的包装类时只要继承这个已有包装类,并修改方法,就能修改被包装对象的功能。

4.2 ServletOutputStream的包装类

本例中ReplaceTextStream类是ServletOutputStream的包装类,它包装了容器提供的ServletOutputStream对象,并覆盖了它的一些方法。在构造包装类实例时,要先把容器提供的ServletOutputStream对象传入。

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
public class ReplaceTextStream extends ServletOutputStream {
private ServletOutputStream intStream; // 容器提供的ServletResponse对象
private ByteArrayOutputStream baStream; // 前组件生成的响应结果的缓存
private boolean closed = false;

private String oldStr; //需要被替换的旧字符串
private String newStr;

public ReplaceTextStream(ServletOutputStream outStream,
String searchStr, String replaceStr) {
intStream = outStream;
baStream = new ByteArrayOutputStream();
oldStr = searchStr;
newStr = replaceStr;
}

public void write(int a)throws IOException{
baStream.write(a);
}

// 把数据写到ByteArrayOutputStream缓存
public void println(String s)throws IOException{
s=s+"\n";
byte[] bs=s.getBytes();
baStream.write(bs);
}

public void close() throws java.io.IOException {
if (!closed) {
processStream();
intStream.close();
closed = true;
}
}

public void flush() throws java.io.IOException {
if (baStream.size() != 0) {
if (! closed) {
processStream();
baStream = new ByteArrayOutputStream();
}
}
}

// 以下两方法在支持非阻塞IO下才需要,本例不支持,故不实现
public void setWriteListener(WriteListener listener){}
public boolean isReady(){return true;}

// 对缓存中的数据进行替换操作,并将结果写到容器提供的ServletOutputStream对象
public void processStream() throws java.io.IOException {
intStream.write(replaceContent(baStream.toByteArray()));
intStream.flush(); //向客户端提交
}
// 替换功能
public byte [] replaceContent(byte [] inBytes) {
String str = new String(inBytes);
return str.replaceAll(oldStr,newStr).getBytes();
}
}

4.3 HttpServletResponse的包装类

本例中的ReplaceTextWrapper是HttpServletResponseWrapper的子类,包装了容器提供的HttpServletResponse对象,覆盖了父类的getOutputStream方法。

ReplaceTextWrapper在构造方法中创建了一个ReplaceTextStream对象,getOutputStream方法返回这个对象,而非返回容器提供的ServletOutputStream。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReplaceTextWrapper extends HttpServletResponseWrapper {

private ReplaceTextStream tpStream; //ServletOutputStream的包装对象

public ReplaceTextWrapper(ServletResponse inResp, String searchText,
String replaceText)throws java.io.IOException {
super((HttpServletResponse) inResp);
tpStream = new ReplaceTextStream(inResp.getOutputStream(),
searchText,replaceText);

}

public ServletOutputStream getOutputStream() throws java.io.IOException {
return tpStream;
}
}

4.4 创建过滤器

本例中的ReplaceTextFilter过滤器,在init初始化中调用config.geetInitParameter(“”)方法读取旧串和新串。

随后doFilter中,创建一个ReplaceTextWrapper对象,调用chain.doFilter(req, myWrapperResp),将ReplaceTextWrapper对象传给后续过滤器或Servlet,本例中是NoteServlet。如此,当NoteServlet调用response.getOutputStream时,获取到的并非容器提供的ServletOutputStream对象,而是包装对象ReplaceTextStream对象。

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
public class ReplaceTextFilter implements Filter {
private FilterConfig config = null;
private String searchStr=null;
private String replaceStr=null;

public void init(FilterConfig config) throws ServletException {
this.config = config;
searchStr=config.getInitParameter("search");
replaceStr=config.getInitParameter("replace");
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//创建Servlet容器提供的ServletResponse的包装类对象
ReplaceTextWrapper myWrappedResp=
new ReplaceTextWrapper( response,searchStr, replaceStr);
config.getServletContext().log("ReplaceTextFilter:before call chain.doFilter()");

//把ServletResponse的包装类传给后续Web组件
chain.doFilter(request, myWrappedResp);

config.getServletContext().log("ReplaceTextFilter:after call chain.doFilter()");
myWrappedResp.getOutputStream().close();
}

public void destroy() {
System.out.println("ReplaceTextFilter:destroy()");
config = null;
}
}

补:本例修改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 发布应用

  1. 编译各个类 [参考3.3 发布步骤](3.3 发布步骤)

  2. 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
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
@WebFilter(
filterName = "AsyncFilter",
urlPatterns = "/async",
asyncSupported=true
)
public class AsyncFilter implements Filter {
// 引用容器提供的FilterConfig对象
private FilterConfig config = null;

public void init(FilterConfig config) throws ServletException {
this.config = config;
}

public void doFilter(ServletRequest request,ServletResponse response,
FilterChain chain) throws IOException, ServletException {
AsyncContext asyncContext = request.startAsync();
// 异步操作超时时间
asyncContext.setTimeout(60*1000);
asyncContext.start(new MyTask(asyncContext));
// 转发请求给后续组件
chain.doFilter(request, response);
}

public void destroy() {
config = null;
}

class MyTask implements Runnable{
private AsyncContext asyncContext;
public MyTask(AsyncContext asyncContext){
this.asyncContext = asyncContext;
}

public void run(){
try{
config.getServletContext().log(
"AsyncFilter:doFilter()");
}catch(Exception e){e.printStackTrace();}
}
}
}

AsyncServlet

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
@WebServlet(name="AsyncServlet",
urlPatterns="/async",
asyncSupported=true)

public class AsyncServlet extends HttpServlet{

public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException{

response.setContentType("text/plain;charset=GBK");
AsyncContext asyncContext = request.getAsyncContext();
asyncContext.start(new MyTask(asyncContext));
}

class MyTask implements Runnable{
private AsyncContext asyncContext;

public MyTask(AsyncContext asyncContext){
this.asyncContext = asyncContext;
}

public void run(){
try{
//睡眠模拟耗时操作
Thread.sleep(5*1000);
asyncContext.getResponse()
.getWriter()
.write("完成");
asyncContext.complete();
}catch(Exception e){e.printStackTrace();}
}
}
}

为使过滤器能够与Servlet共享同一个表示异步处理上下文的AsyncContext对象,可如下创建AsyncContext对象:

1. 在过滤器的doFilter方法中,通过req.startAsync方法,创建AsyncContext对象
2. 在Servlet的service方法中,通过req.getAsyncContext方法获取之前创建的对象
3. 由Servlet的内部类MyTask调用AsyncContext对象的complete方法,提交任务

另,若过滤器使用异步,则串联的过滤器和Servlet都要异步。