原文出处:http://www.oracle.com/technetwork/java/filters-137243.html
简介
Java Servlet规范2.3中引入了一个新的组件类型filter,filter可以动态拦截用户请求(request)和服务器回应(response), 开发者可以对这些数据进行想要的一些操作(例如修改其内容或者获取一些想要的信息)。过滤器一般情况下不会自己生成回应(response),但是它会提供一些通用的可以被加到任意类型servlet和JSP页面中去的功能(关于这些通用功能,后面会详细讲解)。
过滤器很多情况下都很有用:
一方面,过滤器可以将反复出现的任务封装成为一个可重复利用的模块。有条理的开发者们会持续关注哪些可以模块化他们代码的方法,模块化代码会更方便管理和文档化,可以更容易进行debug,如果写的好的话,还有利于移植。
另外一方面,过滤器可以用于对servlet或者JSP页面的回应(response)进行一些转换操作,一个常见的操作是对返回的数据进行格式化,客户端需要越来越多种类的格式化数据(例如WML)而不仅仅是HTML。为了适应这些客户端,需要一个强大的进行数据转换和过滤的组件。启事很多servlet和JSP页面的容器都提供了自己相应版本的过滤机制,这对开发者很有好处,但是各个容器的实现都不一样,导致代码不利于移植,所以,JSP规范对此做出了标准化的规定,这样,开发者写出来的过滤模块就可以被各个容器兼容了。
过滤器可以执行很多不同种类的功能,我们将要讨论下面以斜体字标记的部分(译者注:我将斜体部分标记为了红色):
- 认证: 基于用户身份的客户端请求阻塞
- 日志与审计:跟踪web应用的用户轨迹
- 图像转换: 按比例缩放地图等
- 数据压缩: 让下载的文件变得更小
- 本地化: 将请求和回应定位到特定的地点
- 对XML数据进行XSLT转换:将web应用的应答(response)定位到不同种类的客户端
- 发出请求并执行对应的操作
- 阻止请求和回应向后继续传递
- 修改请求的头部和数据,用一个定制版本的请求来替换。
- 修改回应的头部和数据,用一个定制版本的回应来替换。
- 为了修改web应用的输入与输出你不需要重新编译任何内容。你只需要使用记事本程序或者其它的工具修改配置文件即可。例如,对一个PDF文件下载进行压缩只需要将一个压缩过滤器映射到对应的下载Servlet即可。
- 你可以毫不费力的用过滤器做一些实验,因为它们非常容易配置。
编写过滤器
- 检查请求头部信息
- 修改请求(request)对象:如果它想要修改请求头部或数据或者将请求整个屏蔽掉的时候。
- 修改回应(response)对象:如果它想要修改回应头部或者数据的时候。
- 调用过滤器链(filter chain)中的下一个对象,如果当前filter是过滤器链(filter chain)中最后的一个,那么会调用目标资源,否则,会调用下一个在WAR中配置的过滤器,它通过调用filterchain对象的doFitler方法来达到这个目的(并且将请求对象(request)和回应对象(reponse)或者他们的包装类这两个参数传给doFilter方法)。它可以选择不调用filterchain的doFilter方法,这样请求实际上就被屏蔽掉了,在这种情况下,回应(reponse)对象需要过滤器来进行封装。
- 调用完filterchain的doFilter方法之后检查回应(response)头部
- 处理过程中出现错误跑出异常
例子:记录Servlet访问日志
public final class HitCounterFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)
throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (filterConfig == null)
return;
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
Counter counter = (Counter)filterConfig.
getServletContext().
getAttribute("hitCounter");
writer.println();
writer.println("===============");
writer.println("The number of hits is: " +
counter.incCounter());
writer.println("===============");
// Log the resulting string
writer.flush();
filterConfig.getServletContext().
log(sw.getBuffer().toString());
...
chain.doFilter(request, wrapper);
...
}
}
例子:修改请求(request)编码:
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) throws
IOException, ServletException {
String encoding = selectEncoding(request);
if (encoding != null)
request.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws
ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
}
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}
编程定制请求(request)和回应(response)
PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper(
(HttpServletResponse)response);
chain.doFilter(request, wrapper);
if(wrapper.getContentType().equals("text/html")) {
CharArrayWriter caw = new CharArrayWriter();
caw.write(wrapper.toString().substring(0,
wrapper.toString().indexOf("</body>")-1));
caw.write("<p>\nYou are visitor number
<font color='red'>" + counter.getCounter() + "</font>");
caw.write("\n</body></html>");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
} else {
out.write(wrapper.toString());
}
out.close();
public class CharResponseWrapper extends
HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response){
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter(){
return new PrintWriter(output);
}
}
样例:压缩应答(Conpressing the Response)
public void write(int b) throws IOException {
...
if ((bufferCount >= buffer.length) ||
(count>=compressionThreshold)) {
compressionThresholdReached = true;
}
if (compressionThresholdReached) {
writeToGZip(b);
} else {
buffer[bufferCount++] = (byte) b;
count++;
}
}
样例:改变应答数据
- 将一个公司要求格式的XML文件转换为另外一个公司要求的XML格式
- 根据用户的喜好定制web页面的外观
- 使一个web应用能够向不同种类的客户端做出应答,例如,根据查看User-Agent头部的信息,来判定对方是WML电话还是cHTML电话 ,并选择不同的样式
<book>
<isbn>123</isbn>
<title>Web Servers for Fun and Profit</title>
<quantity>10</quantity>
<price>$17.95</price>
</book>
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="book"> <html>
<body>There are <xsl:value-of select="quantity"/> copies of
<i><xsl:value-of select="title"/></i> available.
</body>
</html>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="book">
<xsl:element name="book">
<xsl:attribute name="isbn"><xsl:value-of select="isbn"/></
xsl:attribute>
<xsl:element name="quantity"><xsl:value-of select="quantity"/
></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
下面的XSLT过滤器根据请求中包含的参数的值使用这和样式将应答转换为不同的格式。过滤器根据请求参数的值来设置应答中的内容类型(content type)。应答被一个CharResponseWrapper包装之后传递给了过滤器链(filter chain)的doFilter方法。过滤器链中的最后一个元素是一个用来返回前述的清单应答的servlet。当doFilter返回的时候,过滤器从包装器中检索应答数据并使用样式进行转换。
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String contentType;
String styleSheet;
String type = request.getParameter("type");
if (type == null || type.equals("")) {
contentType = "text/html";
styleSheet = "/xml/html.xsl";
} else {
if (type.equals("xml")) {
contentType = "text/plain";
styleSheet = "/xml/xml.xsl";
} else {
contentType = "text/html";
styleSheet = "/xml/html.xsl";
}
}
response.setContentType(contentType);
String stylepath=filterConfig.getServletContext().
getRealPath(styleSheet);
Source styleSource = new StreamSource(stylePath);
PrintWriter out = response.getWriter();
CharResponseWrapper responseWrapper =
new CharResponseWrapper(
(HttpServletResponse)response);
chain.doFilter(request, wrapper);
// Get response from servlet
StringReader sr = new StringReader(
new String(wrapper.getData()));
Source xmlSource = new StreamSource((Reader)sr);
try {
TransformerFactory transformerFactory =
TransformerFactory.newInstance();
Transformer transformer = transformerFactory.
newTransformer(styleSource);
CharArrayWriter caw = new CharArrayWriter();
StreamResult result = new StreamResult(caw);
transformer.transform(xmlSource, result);
response.setContentLength(caw.toString().length());
out.write(caw.toString());
} catch(Exception ex) {
out.println(ex.toString());
out.write(wrapper.toString());
}
}
指定过滤器配置
- 在web应用部署描述文件中,使用<filter>元素声明一个过滤器。这个元素为过滤器创建一个名字并声明过滤器的时候类和初始化参数。
- 使用部署描述文件中的<filter-mapping>元素将过滤器映射到一个servlet。这个元素使用名字或者URL模式来将一个过滤器映射到一个servlet。
<filter>
<filter-name>Compression Filter</filter-name>
<filter-class>CompressionFilter</filter-class>
<init-param>
<param-name>compressionThreshold</param-name>
<param-value>10</param-value>
</init-param>
</filter>
filter-mapping元素将压缩过滤器映射到哦啊conpresionTest这个servlet。映射中也指定了URL模式为/compressionTest。需要注意的是filter,filter-mapping, servlet和servlet-mapping这几个元素需要按顺序依次出现。
<filter-mapping>
<filter-name>Compression Filter</filter-name>
<servlet-name>CompressionTest</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>CompressionTest</servlet-name>
<servlet-class>CompressionTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CompressionTest</servlet-name>
<url-pattern>/CompressionTest</url-pattern>
</servlet-mapping>
需要注意的是,这个映射会让所有的发给ConpressionTest的请求或者任何映射到/CompressionTest URL的servlet JSP或者静态内容都会导致这个过滤器被调用。
?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN" "http://bit.ly/15eRp2z">
<web-app>
<filter>
<filter-name>XSLTFilter</filter-name>
<filter-class>XSLTFilter</filter-class>
</filter>
<filter>
<filter-name>HitCounterFilter</filter-name>
<filter-class>HitCounterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HitCounterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>XSLTFilter</filter-name>
<servlet-name>FilteredFileServlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>FilteredFileServlet</servlet-name>
<servlet-class>FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FilteredFileServlet</servlet-name>
<url-pattern>/ffs</url-pattern>
</servlet-mapping>
</web-app>
就像你所看到的,你可以将一个过滤器映射到一个或者多个servlet上,你也可以将多个过滤器映射到一个servlet上。