什么是Servlet过滤器(filter)? Servlet 过滤器是小型的 Web 组件,可以链在Servlet容器的处理过程中,拦截请求和响应,检查和修在客户机和Web应用程序之间交换的数据。这意味着过滤器会在Servlet处理之前访问一个进入的请求,并在外发的响应回到客户前访问这些信息。
过滤器可以被添加到请求/响应链中,或者在无需影响应用程序中其他 Web 组件的情况下删除它们。过滤器仅只是改动请求和响应的运行时处理,因而不应该将它们直接嵌入 Web 应用程序框架。
Servlet可以与一个或者多个过滤器相关联,后者将形成一个过滤器链。
编写一个 Servlet 过滤器 实现一个 Servlet 过滤器需要三个步骤:首先要编写过滤器实现类的程序,然后要把该过滤器添加到 Web 应用程序中(通过在 Web 部署描述符 web.xml 中声明它),最后要把过滤器与应用程序一起打包部署。
1. 编写实现类的程序
过滤器 API 一共包含 3 个简单的接口:Filter、FilterChain 和 FilterConfig。过滤器类必须需要实现 Filter 接口:
init():这个方法在容器实例化过滤器时被调用,它主要设计用于使过滤器为处理做准备。容器为这个方法传递一个FilterConfig对象,其中包含着配置信息。
doFilter():过滤器拥有单个用于处理请求和响应的方法——doFilter()。这个方法接受三个输入参数:一个 ServletRequest、response 和一个 FilterChain 对象。FilterChain对于正确的过滤操作至关重要。doFilter()方法必须调用FilterChain的doFilter()方法,除非该方法用来拦截以后的下游处理。注意:过滤器的一个实例可以同时服务于多个请求,意味着任何共享的变量都必须通过同步块(synchronized block)来访问。
destroy():该方法由容器在销毁过滤器实例之前调用。
例1 演示了一个简单的过滤器,用来计算一个客户机的 Web 请求所花的大致时间。
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
|
package cc.ejb.examples;
import java.io.IOException;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class PageTimerFilter implements Filter
{
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
{
Date startTime, endTime;
double duration;
startTime = new Date();
chain.doFilter(request, response);
endTime = new Date();
duration = (endTime.getTime() - startTime.getTime())/1000;
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
writer.println();
writer.println("===============");
writer.println("Total elapsed time is: " + duration + " seconds.");
writer.println("===============");
writer.flush();
config.getServletContext().log(sw.getBuffer().toString());
}
public void destroy() {
this.config=null;
}
}
|
在doFilter()方法实现中,出现在FilterChain的doFilter()方法调用之前的代码都被看成是预处理,Web资源(包括其他过滤器、Servlet等等)所做的处理还没有发生。而在该方法之后的代码则是后期处理,这时外发的响应信息已经包含了Web资源的完整响应。也就是说,
FilterChain的doFilter()将调用接下来的过滤器(在有链式关系的时候)或者其他Web资源。 2. 配置 Servlet 过滤器和配置Servlet类似,过滤器通过 web.xml 文件中的两个 XML 标签<filter>和<filter-name>来声明。<filter>标签负责把一个过滤器名和一个特定的类关联起来,这种关联是通过<filter-name>和<filter-class>元素指定。其 DTD定义如下:
1
|
(((description*,display-name*,icon*)),filter-name,filter-class,init-param*)
|
可以为过滤器指定初始化参数,和Servlet类似,参数是使用<inti-param>和成对的<param-name>和<param-value>来指定的,如下所示:
1 2 3 4
|
<init-param>
<param-name>counter</param-name>
<param-value>100</param-value>
</init-param>
|
例 2 显示了 web.xml 文件,它展示了如何声明过滤器的包含关系:
1 2 3 4 5 6 7 8
|
<filter>
<filter-name>Page Timers</filter-name>
<filter-class>cc.ejb.examples.PageTimerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Page Timers</filter-name>
<url-pattern> |
<filter>必须有一个<ulr-pattern>或者<servlet-name>元素。我们可以通过<ulr-pattern>来指定通配符,将过滤器应用到Web资源范围,在上面的例子中,Page Timer过滤器被应用到每个Web资源。也可以通过<servlet-name>将过滤器指定到某一个特定的Servlet上。应该注意这些声明的顺序,所引用的过滤器名必须在前面的过滤器定义给出。
3. 部署 Servlet 过滤器事实上, 部署过滤器是非常简单的事情。只需把过滤器类和其他 Web 组件类包括在一起,并像通常所做的那样把 web.xml 文件(连同过滤器定义和过滤器映射声明)放进 Web 应用程序结构中,servlet 容器将处理之后的其他所有事情。
Servlet 2.4中的新特性 我们通过下面的例子来研究Servlet 2.4的新特性。例3和例4是两个很简单的程序片断,JSP程序将来自客户的请求forward到Thank.html。
1 2 3 4 5 6
|
<%@ page language="java" %>
<html>
<body>
<jsp:forward page="/Thank.html"/>
</body>
</html>
|
1 2 3 4 5
|
<html>
<body>
Thank you for coming Filter worlds<br>
</body>
</html>
|
将这两个程序和上面的过滤器一起打包部署并运行Test.jsp,我们发现控制台只有一行而不是两行输出:
1 2 3 4
|
11:06:57,045 INFO [Engine] StandardContext[/Test]
===============
Total elapsed time is: 0.0 seconds.
===============
|
因为,在Servlet 2.3 规范中的过滤器只能过滤 Web 客户机和其所访问的指定 Web 资源之间的内容。如果该资源然将请求调度给其他 Web 资源(这里是Thank.html),就不能向幕后委托的任何请求应用过滤器。
2.4 规范消除了这个限制,通过增强filter和request dispatcher的配合,过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。该功能是通过中的元素来实现的:
只有当request直接来自客户,过滤器才生效,对应为REQUEST条件。
只有当request被一个请求分发器使用forward()方法转到一个Web构件时(采用或定义),对应称为FORWARD条件。
类似地,只有当request被一个请求分发器使用include()方法转到一个Web构件时(采用或定义),对应称为INCLUDE条件。
只有当request被一个请求分发器使用“错误信息页”机制方法转到一个Web构件时,对应称为ERROR条件。
以上四种条件的组合使用。
修改之后web.xml的如下所示:
1 2 3 4 5 6 7 8 9 10
|
<filter>
<filter-name>Page Timers</filter-name>
<filter-class>cc.ejb.examples.PageTimerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Page Timers</filter-name>
<url-pattern> |
再次部署运行这个Web应用,结果如下:
1 2 3 4 5 6 7 8
|
11:17:51,165 INFO [Engine] StandardContext[/Test]
===============
Total elapsed time is: 0.0 seconds.
===============
11:17:51,165 INFO [Engine] StandardContext[/Test]
===============
Total elapsed time is: 10.0 seconds.
===============
|
过滤器的运用
在适合使用装饰过滤器模式或者拦截器模式的任何地方,您都可以使用过滤器。过滤器的一些最普遍的应用如下:
加载:对于到达系统的所有请求,过滤器收集诸如浏览器类型、一天中的时间、转发 URL 等相关信息,并对它们进行日志记录。
性能:过滤器在内容通过线路传来并在到达 servlet 和 JSP 页面之前解压缩该内容,然后再取得响应内容,并在将响应内容发送到客户机机器之前将它转换为压缩格式。
安全:过滤器处理身份验证令牌的管理,并适当地限制安全资源的访问,提示用户进行身份验证和/或将他们指引到第三方进行身份验证。过滤器甚至能够管理访问控制列表(Access Control List,ACL),以便除了身份验证之外还提供授权机制。将安全逻辑放在过滤器中,而不是放在 servlet 或者 JSP 页面中,这样提供了巨大的灵活性。在开发期间,过滤器可以关闭(在 web.xml 文件中注释掉)。在生产应用中,过滤器又可以再次启用。此外还可以添加多个过滤器,以便根据需要提高安全、加密和不可拒绝的服务的等级。
会话处理:将 servlet 和 JSP 页面与会话处理代码混杂在一起可能会带来相当大的麻烦。使用过滤器来管理会话可以让 Web 页面集中精力考虑内容显示和委托处理,而不必担心会话管理的细节。
XSLT 转换:不管是使用移动客户端还是使用基于 XML 的 Web 服务,无需把逻辑嵌入应用程序就在 XML 语法之间执行转换的能力都绝对是无价的。