[译] The Essentials of Filters (I)
原文:http://java.sun.com/products/servlet/Filters.html
Java Servlet2.3版本的规范提供了一种新的组件类型,叫做过滤器(fileter)。一个过滤器通过动态的拦截Request和Response来转换或者使用请求和响应中包含的信息。过滤器通常并不自己创建Response,但提供可以“附着”于任何类型的Servlet或JSP页面的全局函数。
很多原因都说明过滤器是很重要的。首先,他提供了在可重用单元中封装重复代码的能力。有经验的程序员始终坚持不懈的追求代码模块化的方法。模块化的代码更加可管理和可文档化,也易于调试。而且如果代码模块化的好的化,可以在另外一个框架中被使用。
其次,过滤器可以用来从Servlet和JSP改变response。一个很常见的Web应用是格式化数据并将数据返回至客户端。越来越多的客户端需要格式(例如:WML)而不仅仅是HTML。为了兼容这些客户端,在一个功能完备的Web应用中通常有一个功能强大的转换或者过滤组件。许多Servlet和JSP容器已经提供了自己的过滤器机制,这样使得发布程序到同一容器的开发者受益匪浅,但是这些的代码的可重用性减弱了。现在过滤器作为Java Servlet规范的一部分,开发者有机会按照它的规范来编写容器间可以互相移植的、可重用的转换组件。
过滤器可以实现很多不同类型的功能。我们会在本文中讨论:
● 认证-根据用户身份阻塞Request(本文不做讨论)。
● 日志和审计-跟踪Web应用的用户。
●映射转换-缩放映射,等等(本文不作讨论)。
●数据压缩-使下载文件更小。
●本地化-指定Request和Response为某个特定区域。
● XML 内容的 XSL/T 转换-指定Web应用的Response到多个类型的客户端。
过滤器应用程序并不多,但是象加密,字符解析,触发资源访问事件,改变mime-type和缓存处理的程序却很多。
本文我们将探讨如何编写过滤器来完成以下的任务。
●查询Request并执行相应的程序。
●阻止Request和Response往后传递。
● 修改Request头和数据。你可以通过提供一个定制的Request版本来实现。
● 修改Response头和数据。你可以通过提供一个定制的Response版本来实现。
我们简单说明一下过滤器的API,并且描述如何开发定制的Request和Response。
编写过滤器是使用过滤器的工作的一半----当应用程序被部署到了网络应用程序容器中时,你同样需要配置来表示他们是如何被映射到Servlets的。这种编程和配置的解耦是过滤器机制的最主要好处。
● 你不需要重新编译任何代码来改变你的网络应用程序的输入或者输出,你只需要编辑一个文本
文件或使用一个工具来改变配置。例如,要添加一个压缩成PDF文档供下载的功能,只需要
将压缩过滤器映射到实现下载功能的那个Servlet就可以了。
●你可以很容易的用过滤器来试验,因为他们是如此的易于配置。
本文的最后一段演示了如何使用非常复杂的过滤器配置机制。一旦你读完了这篇文章,这些方法会对你实现你自己的过滤器有很大的帮助,你自己也会掌握一些使用常见的过滤器类型的诀窍。
编写过滤器:
过滤器API由javax.servlet包中的Filter,FilterChain和FilterConfig接口定义。你可以通过实现Filter接口来定义一个过滤器。一个过滤器链通过容器传给一个过滤器,它提供一种调用一连串的过滤器的机制。一个filter config 包含了一些原始数据。
Filter接口中最重要的方法是doFilter方法,它是过滤器的核心。这个方法通常实现以下功能。
● 检查Request头。
●在过滤器要修改Request头或数据的时候定制Request对象或者完全阻塞Request通过。
● 在过滤器要修改Response头或数据的时候定制Response对象。
●调用过滤器链中的下一个实体。如果当前的过滤器是链中的以目标Servlet结尾的最后一个
过滤器,下一个实体就是链尾部的资源;否则,它是在WAR中设定的下一个过滤器。它通过
调用链对象的doFilter方法来调用下一个实体。(调用传递Request和Response,也可能
传递它创建的包装过的Request,Response版本)另外,它可以选择不去调用下一个实体
来去阻止Request。在后者中,过滤器有责任填充Response。
● 在调用链中的下一个过滤器后检查Response头。
● 抛出异常指示处理错误。
除了doFilter之外,你还必须实现init和destroy方法。init方法在过滤器实例化的时候被调用的。如果你想给过滤器传递初始化参数,你可以从FilterConfig对象中重新得到,然后传给init方法。
例子:访问Servlet的日志。
现在你已经知道过滤器API的主要元素是什么了,我们来看看一个非常简单的过滤器,这个过滤器不阻塞request,不转换response,也没有任何可以成为开始学习API的基本概念的奇思妙想。
考虑那些可以跟踪用户的网站。在一个已经存在的Web应用中不修改任何servlet而想要增加这样的功能,你可以使用一个日志过滤器。
HitCounterFilter递增并记录每次访问一个servlet时的计数器值。在doFilter方法里,HitCounterFilter首先从过滤器配置对象中重新得到servlet的上下文,然后它就可以访问作为上下文属性存储起来的计数器。在过滤器重新获得计数器、递增计数器和将计数器值写入日志后,它调用过滤器链对象的doFilter方法来返回到原来的doFilter方法。省略的代码将在"编写定制的Request和Response"一段中讨论。
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的字符编码
现在很多浏览器不在发送HTTP请求的时候在它的Content-Type头中包含字符编码信息。如果客户端请求没有指定编码,容器使用默认的编码来解析Request的参数。如果客户端没有设置字符编码并且Request参数采用的是一种不同于默认编码的字符编码,参数就会被错误的解析。你可以在ServletRequest接口中使用SetCharacterEncoding方法来设置编码。因为这个方法必须在解析任何提交的数据或者读取任何Request中的输入之前被调用,所以这个方法是过滤器首先要执行的程序。
这样的过滤器包含在随Tomcat4.0 Web容器一起发布的例子中。这个过滤器设置Request编码为过滤器初始化参数中的字符编码。这个过滤器可以被很容易的扩展,以使其可以根据Request的特征来设置编码,如Accept-Language和User-Agent头的值,或者在当前用户的Session中保存的某个值。
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);
}
(未完待续)

浙公网安备 33010602011771号