
3.10 Filter
Filter(过滤器)是Servlet 2.3规范以后增加的新特性。Filter拦截请求和响应,以便查看、提取或以某种方式操作正在客户端和服务器之间交换的数据。Filter可以改变一个请求(Request)或者是修改响应(Response)。Filter与Servlet的关联由Web应用的配置描述文件或注解来明确。用户发送请求给Servlet时,在Servlet处理请求之前,与此Servlet关联的Filter首先执行,然后才是Servlet的执行,Servlet执行完毕又会回到Filter。如果一个Servlet有多个Filter,则根据配置的先后次序依次执行。
Filter主要用在以下几个方面:
(1)访问特定资源(Web页、JSP页、Servlet)时的身份验证。
(2)访问资源的记录跟踪。
(3)访问资源的转换。
一个Filter必须实现javax.Servlet.Filter接口,即实现下面的三个方法:
(1)doFilter(ServletRequest, ServletResponse, FilterChain)。用来实现过滤行为的方法。引入的FilterChain对象提供了后续Filter所要调用的信息。
(2)init(FilterConfig)。由容器所调用的Filter初始化方法。容器确保在第一次调用doFilter方法前调用此方法,一般用来获取在web.xml文件中指定的初始化参数。
(3)destroy()。容器在破坏Filter实例前,doFilter()中的所有活动都被该实例终止后,调用该方法。
下面演示如何利用Filter来记录Web组件对请求的响应时间。首先生成一个Filter。在“项目”视图中选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“新建”→“文件/文件夹”命令,弹出“新建文件”对话框,如图3-37所示。

图3-37 创建Filter
在“类别”列表中选中Web,在“文件类型”列表中选中“过滤器”,单击“下一步”按钮,得到如图3-38所示的“New过滤器”对话框。

图3-38 Filter的名称和位置
如图3-38所示,在“类名”文本框中输入Filter的名称TimeTrackFilter,在“包”文本框中输入包的名称com.servlet,单击“下一步”按钮,得到如图3-39所示的对话框。

图3-39 配置Filter部署
在这里要配置过滤器的部署信息,即将过滤器与它要过滤的Web组件或URL模式关联起来。
在“过滤器名称”文本框中可以输入过滤器的逻辑名称,这里采用默认选项。单击“编辑”按钮打开“过滤器映射”对话框来设置过滤器映射信息,如图3-40所示。

图3-40 “过滤器映射”对话框
说明:过滤器有两种映射模式。一种是对URL模式的映射,这也是默认的映射模式。在URL模式中可以使用通配符号,如“/*”。另外一种模式是对Servlet的映射,这时过滤器关联的是Servlet的逻辑名称。
选中单选按钮URL,并在其右侧的文本框中输入“/Main”。单击“确定”按钮完成过滤器映射配置。此时Filter关联的URL对应的组件为3.9节创建的Servlet Main。最后单击图3-39中的“完成”按钮,Filter创建完毕。完整代码如程序3-33所示。
程序3-33:TimeTrackFilter.java
… @WebFilter(filterName = "TimeTrackFilter", urlPatterns = {"/Main"}) public class TimeTrackFilter 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 { Date startTime, endTime; double totalTime; StringWriter sw = new StringWriter(); System.out.println("我在Filter中"); startTime = new Date(); chain.doFilter(request, response); endTime = new Date(); totalTime = endTime.getTime() - startTime.getTime(); totalTime = totalTime ; System.out.println("我在Filter中"); PrintWriter writer = new PrintWriter(sw); writer.println("==============="); writer.println("耗时:" + totalTime + " 毫秒" ); writer.println("==============="); filterConfig.getServletContext(). log(sw.getBuffer().toString()); } }
程序说明:跟Servlet一样,在新版本的Java EE规范中,提供了注解WebFilter来部署Filter组件,其中属性filterName为Filter的逻辑名称,属性urlPatterns为Filter的URL模式。
程序包含了所有Filter必须实现的3个接口方法:init、destroy和doFilter。当容器第一次加载该过滤器时,init方法将被调用。该类在这个方法中包含了一个指向FilterConfig对象的引用。对请求和响应的过滤功能主要由doFilter实现。Web容器在垃圾收集之前调用destroy方法,以便能够执行任何必需的清理代码。
TimeTrackFilter主要实现对过滤的Web组件处理耗时的跟踪,在调用FilterChain对象的doFilter(request, response)方法之前创建一个Date对象startTime, FilterChain对象在doFilter(request, response)方法执行完毕后,控制权仍旧回到当前的Filter,此时,再创建一个Date对象endTime来获取当前时刻,二者相减,就得到被过滤Web组件的执行时间。
为了使执行效果更明显,可以修改Servlet Main,使Servlet的线程暂时中止2秒。修改后的代码如程序3-34所示。
程序3-34:Main.java
package com.servlet; … public class Main extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ System.out.println("我在Servlet Main中"); try{ Thread.sleep(2000); }catch (InterruptedException ie){ System.out.println(ie.toString()); } String userID=request.getParameter("userID"); … }
程序说明:斜体部分为新增的代码,即调用Thread对象的sleep方法使线程暂停2秒。
重新发布Web应用,打开IE浏览器,在地址栏中输入http://localhost:8080/Chapter3/dl.html,得到如图3-34所示的运行结果页面。在“用户名”文本框输入guest,在“密码”文本框输入guest,单击“提交”按钮,此时浏览器向URL模式“/Main”发出请求,由于URL模式“/Main”被关联到Filter TimeTrackFilter,则首先执行TimeTrackFilter的doFilter方法,然后执行Servlet Main,最后又回到Filter的doFilter方法。因此在Netbeans底部的“输出”窗口的GlassFish Server 3.1.1中可以看到如图3-41的输出信息。

图3-41 Filter运行过程中的输出信息
从上面的演示过程可以看出,开发人员不应当把Filter看作是请求到达Servlet之前的一道防火墙,而应当把它看作是包裹在Servlet组件外面的一层防护网。在请求到达Servlet前后都会经过Filter的处理。
Filter不仅可以对URL模式进行过滤,还可以对Servlet组件的逻辑名称进行过滤。下面为TimeTrackFilter添加对Servlet组件PDFServlet的过滤。打开web.xml,在编辑器中选中“过滤器”视图,得到如图3-42所示的运行界面。

图3-42 修改Filter配置信息
单击“过滤器映射”下的“添加”按钮,弹出“编辑过滤器映射”对话框,如图3-43所示。

图3-43 修改Filter配置信息
选中“Servlet名称”单选按钮,在其右边的下拉列表中选中要过滤的Servlet的名称PDFServlet,单击“确定”按钮,完成TimeTrackFilter对Servlet组件PDFServlet的过滤设置。
重新发布Web应用,打开IE浏览器,在地址栏中输入http://localhost:8080/Chapter3/pdfshow,在Netbeans底部的“输出”窗口的GlassFish Server 3.1.1视图中可以看到执行Servlet组件PDFServlet的耗时信息。
在Servlet 2.4以上的Web容器中,过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。在图3-43中,在“分发程序类型”组中可以看到四个检查框,分别代表以下分发类型:
·REQUEST——只有当request直接来自客户,过滤器才生效。
·FORWARD——只有当request被一个请求分发器使用forward方法转到另一个Web组件时,过滤器才生效。
·INCLUDE——只有当request被一个请求分发器使用include方法转到一个Web构件时,过滤器才生效。
·ERROR——只有当request被一个请求分发器使用“错误信息页”机制方法转到一个Web组件时,过滤器才生效。
以上四个条件可以组合使用。
在图3-39中,选中Filter的映射项PDFServlet,单击“编辑”按钮来修改映射信息。在弹出的“编辑过滤器映射”对话框中,选中FORWARD复选框,如图3-44所示。单击“确定”按钮,完成对过滤器映射信息的修改。

图3-44 修改Filter映射信息
重新发布Web应用,打开IE浏览器,在地址栏中输入http://localhost:8080/Chapter3/pdfshow,在Netbeans底部的“输出”窗口的GlassFish Server 3.1.1视图中将看不到Filter输出的执行Servlet组件PDFServlet的耗时信息。因为按照修改后的Filter映射设置,只有调用请求分发器的forward方法对Servlet组件PDFServlet发送请求才被过滤器过滤。