Tomcat的过滤诀窍
Servlet 2.3 规范的附加过滤为 J2EE 应用程序提供了强化的性能Sing Li (westmakaha@yahoo.com)
作家, Wrox 出版社
2001 年 6 月
新的 Java Servlet 2.3 规范有不少最激动人心的功能,其中之一便是过滤。乍一看,Servlet 2.3 过滤似乎与 Apache、IIS、Netscape Web 服务器及其它服务器中已有的传统过滤器非常相似。事实上,Servlet 2.3 过滤从结构上来说是一个完全不同的设计 -- 补充支持 Java 平台面向对象的特性,以提供更高级别的性能。本文向您介绍了 Tomcat 4 中的过滤,并展示了如何在项目中高效地使用过滤器。请点击文章顶部或底部的 讨论,参与讨论论坛,与本文作者和其他读者分享您对本文的想法。
过滤是 Tomcat 4 的新功能。(如想了解 Tomcat 的简要历史,请参阅 Tomcat 的故事)。它是 Servlet 2.3 规范的一部分,并且最终将为所有支持此标准的 J2EE 容器的厂商所采用执行。开发人员将能够用过滤器来实现以前使用不便的或难以实现的功能,这些功能包括:
* 资源访问(Web 页、JSP 页、servlet)的定制身份认证
* 应用程序级的访问资源的审核和记录
* 应用程序范围内对资源的加密访问,它建立在定制的加密方案基础上
* 对被访问资源的及时转换,包括从 servlet 和 JSP 的动态输出
这个清单当然并没有一一罗列,但它让您初步体验到了过滤所带来的额外价值。在本文中,我们将详细讨论 Servlet 2.3 的过滤,来看一看过滤器是如何配合 J2EE 处理模型的。不像其它传统的过滤方案,Servlet 2.3 过滤是建立在嵌套调用的基础上的。我们来研究一下这一差别是怎样在架构上与新的高性能 Tomcat 4 设计取得一致的。最后,我们将获得一些编写及测试两个 Servlet 2.3 过滤器的实际经验。这些过滤器只完成很简单的功能,使我们得以将注意力集中于编写过滤器以及如何将它们集成进 Web 应用程序的机制。
作为 Web 应用程序构建模块的过滤器
在物理结构上,过滤器是 J2EE Web 应用程序中的应用程序级的 Java 代码组件。除了 servlet 和 JSP 页以外,遵循 Servlet 2.3 规范编码的开发人员能将过滤器作为在 Web 应用程序中加入活动行为的机制。与在特定的 URL 上工作的 servlet 和 JSP 页不同,过滤器接入 J2EE 容器的处理管道,并能跨越由 Web 应用程序提供的 URL 子集(或所有 URL)进行工作。图 1 说明了过滤是在哪里配合 J2EE 请求处理的。
图 1. 过滤器与 J2EE 请求处理
[myimg]upload/fig1.png[/myimg]
兼容 Servlet 2.3 的容器允许过滤器在请求被处理(通过 Servlet 引擎)以前以及请求得到处理以后(过滤器将可以访问响应)访问 Web 请求。
在这些情况下,过滤器可以:
* 在请求得到处理以前修改请求的标题
* 提供它自己的请求版本以供处理
* 在请求处理以后和被传回给用户以前修改响应
* 先取得由容器进行的所有请求处理,并产生自己的响应
比过滤器的可用性更为重要的是,接入 J2EE 处理管道需要创建不可移植的、容器专用的和系统范围的扩展机制(如 Tomcat 3 易做图)。
概念上的 Tomcat 过滤
不同于在 Apache、IIS 或 Netscape 服务器中能找到的熟悉的过滤机制,Servlet 2.3 过滤器并非建立在挂钩式函数调用上。事实上, Tomcat 4 级别的引擎架构脱离了传统的 Tomcat 3.x 版本。新的 Tomcat 4 引擎取代了在请求处理的不同阶段调用挂钩式方法的整体式引擎,它在内部使用了一系列的嵌套调用、包装请求及响应。不同的过滤器和资源处理器构成了一个链。
在传统架构中:
* 每次接受到请求,挂钩式方法就被调用,不论它们是否执行(有时甚至是空的)。
* 方法的作用域及并发关系(每个方法可能在不同的线程上被调用)不允许在处理相同的请求时简单、高效地共享不同挂钩式方法调用间的变量和信息。
在新架构中:
* 嵌套的方法调用通过一系列过滤器实现,它仅有应用于当前请求的过滤器组成;基于挂钩式调用的传统执行方式需要在处理短句中调用挂钩式例程,即使一个特定短句的处理逻辑不起任何作用。
* 局部变量在实际的过滤方法返回之前都作保留,并且可用(因为上游过滤器的调用总在堆栈上,等待后续调用的返回)。
这一新架构为今后的 Tomcat 性能调整与优化提供了一个新的、更对象友好的基础。Servlet 2.3 过滤器是这个新的内部架构的自然扩展。该架构为 Web 应用程序设计人员提供了一个可移植的执行过滤行为的方法。
调用链
所有过滤器都服从调用的过滤器链,并通过定义明确的接口得到执行。一个执行过滤器的 Java 类必须执行这一 javax.servlet.Filter 接口。这一接口含有三个过滤器必须执行的方法:
* doFilter(ServletRequest, ServletResponse, FilterChain):这是一个完成过滤行为的方法。这同样是上游过滤器调用的方法。引入的 FilterChain 对象提供了后续过滤器所要调用的信息。
* init(FilterConfig):这是一个容器所调用的初始化方法。它保证了在第一次 doFilter() 调用前由容器调用。您能获取在 web.xml 文件中指定的初始化参数。
* destroy():容器在破坏过滤器实例前,doFilter()中的所有活动都被该实例终止后,调用该方法。
请注意:Filter 接口的方法名及语义在最近的几个 beta 周期中曾有过不断的改变。Servlet 2.3 规范仍未处于最后的草案阶段。在 Beta 1 中,该接口包括 setFilterConfig() 和 getFilterConfig() 方法,而不是 init() 和 destroy()。
嵌套调用在 doFilter() 方法执行中发生。除非您建立一个过滤器明确阻止所有后续处理(通过其它过滤器及资源处理器),否则过滤器一定会在 doFilter 方法中作以下的调用:
FilterChain.doFilter(request, response);
安装过滤器:定义与映射
容器通过 Web 应用程序中的配置描述符 web.xml 文件了解过滤器。有两个新的标记与过滤器相关:<filter> 和 <filter-mapping>。应该指定它们为 web.xml 文件内 <web-app> 标记的子标记。
过滤器定义的元素
<filter> 标记是一个过滤器定义,它必定有一个 <filter- name> 和 <filter-class> 子元素。<filter-name> 子元素给出了一个与过滤器实例相关的、基于文本的名字。<filter-class> 指定了由容器载入的实际类。您能随意地包含一个 <init-param> 子元素为过滤器实例提供初始化参数。例如,下面的过滤器定义指定了一个叫做 IE Filter 的过滤器:
清单 1. 过滤器定义标记
<web-app>
<filter>
<filter-name>IE Filter</filter-name>
<filter-class>com.ibm.devworks.filters.IEFilter</filter-class>
</filter>
</web-app>
容器处理 web.xml 文件时,它通常为找到的每个过滤器定义创建一个过滤器实例。这一实例用来服务所有的可用 URL 请求;因此,以线程安全的方式编写过滤器是最为重要的。
过滤器映射及子元素
<filter-mapping> 标记代表了一个过滤器的映射,指定了过滤器会对其产生作用的 URL 的子集。它必须有一个 <filter-name> 子元素与能找到您希望映射的过滤器的过滤器定义相对应。接下来,您可以使用 <servlet-name> 或 <url-pattern> 子元素来指定映射。<servlet-name> 指定了一个过滤器应用的 servlet (在 web.xml 文件中的其它地方已定义)。您能使用 <url-pattern> 来指定一个该过滤器应用的 URL 的子集。例如, /* 的样式用来代表该过滤器映射应用于该应用程序用到的每个 URL,而 /dept/humanresources/* 的样式则表明该过滤器映射只应用于人力资源部专有的 URL。
容器使用这些过滤器映射来确定一个特定的过滤器是否应参与某个特定的请求。清单 1 是为应用程序的所有 URL 定义的应用于 IE Filter 的一个过滤器映射:
清单 2. 过滤器映射标记
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
创建一个简单的过滤器
现在该来定义我们的第一个过滤器了。这是一个不重要的过滤器,检查请求标题以确定是不是使用 Internet Explorer 浏览器来查看 URL 的。如果是 Internet Explorer 浏览器,过滤器就显示“拒绝访问”的信息。尽管操作并不重要,但这个示例演示了:
* 一个过滤器的一般剖析
* 一个在请求到达资源处理器前检查其标题信息的过滤器
* 如何编写一个过滤器来阻止基于运行时间检测到的条件(验证参数、源 IP、时间…等等)的后续处理
此过滤器的源代码作为 IEFilter.java,com.ibm.devworks.filters 包的一部分位于源代码发布区中(请参阅参考资料)。现在就让我们来仔细研究一下该过滤器的代码。
清单 3. 使用 Filter 接口
public final class IEFilter implements Filter {
private FilterConfig filterConfig = null;
所有的过滤器