过滤器(filter)是Java类,可以改变请求(request)和响应(response)的头信息与内容信息。过滤器不同于其他Web组件的地方是它本身并不创建响应(response),然而它可以依附在任何类型的Web资源上。过滤器截取请求(request),检查和改变request对象、response对象,并可以执行一些其他的任务。过滤器提供的主要功能是:
-
实现日志功能
-
实现用户定义的安全功能
-
调试功能
-
加密
-
数据压缩
-
改变发送给客户端的响应(response)
过滤器截获对特定命名的一个资源和一组资源的请求(request),然后执行过滤器中的代码。对于特定的资源,可以指定按照一定顺序调用的一个和多个过滤器,这就组成了链(chain)。使用过滤器主要包括:
-
编写过滤器类
-
定制请求(request)和响应(response)
-
为特定的Web资源指定过滤器链
编写过滤器的API是javax.servlet包中Filter、FilterChain和FilterConfig接口中定义的一些方法。定义一个过滤器就是实现Filter接口。Filter接口中最主要的方法是doFilter()方法,它接收三个参数:request对象、response对象、filterchain对象。
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)这个方法能够执行的动作包括:
-
检查请求(request)的头信息
-
定制request对象,改变请求(request)的头信息或数据
-
定制response对象,改变响应(response)的头信息或数据
-
调用在过滤器链中的下一个实体。如果当前过滤器是链中的最后一个过滤器,那么下一个实体就是客户请求(request)的资源;否则,链中的下一个过滤器会被调用。通过chain对象的doFilter()方法调用下一个实体,并传递request对象和response对象作为参数。另外,也可以不调用doFilter()方法阻塞请求(request),这样,过滤器应该负责填充对客户的响应(response)。
-
检查响应的头信息
-
抛出异常显示处理过程中的错误
除了doFilter()方法,开发人员也必须实现init()和destroy()方法。当容器创建过滤器实例时调用init()方法,
void init(FilterConfig filterConfig)可以从FilterConfig对象中获得初始化参数。
在doFilter()方法中,过滤器可以从FilterConfig对象获得ServletContext对象,那么就可以访问存储在ServletContext中的属性对象。当过滤器完成特定的处理过程后,调用chain对象的doFilter()方法。例如
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); writer.println(" ... "); . . . writer.flush(); // 输出日志 filterConfig.getServletContext(). log(sw.getBuffer().toString()); ... //调用在过滤器链中的下一个实体 chain.doFilter(request, wrapper); ... }} 6.5.1.2. 定制请求和响应有许多方法可以改变请求(request)或者响应(response)。例如过滤器能够给请求(request)增加属性或在响应(response)中插入数据。
过滤器如果需要改变响应(response)必须在响应(response)返回给客户端之前捕获它。要做到这一点需要传递“替身”流(stream)给Servlet,然后利用“替身”stream产生响应(response)。“替身”stream防止了当响应(response)结束后关闭了原始的响应(response)输出流,并且允许过滤器改变Servlet的响应(response)。
过滤器产生“替身”stream
为了给Servlet传递“替身”stream,过滤器需要创建response对象的包装类并且覆盖getWriter或getOutputStream方法。包装类被FilterChain对象的doFilter方法传递。创建请求(request)的包装类继承ServletRequestWrapper或HttpServletRequestWrapper,创建响应(response)的包装类继承ServletResponseWrapper或HttpServletResponseWrapper。
以下CharResponseWrapper类包装了响应(response):
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); }}包装类被传递给BookStoreServlet,BookStoreServlet把响应写入“替身”stream,当chain.doFilter返回,HitCounterFilter重新找回response把它写入缓冲,过滤器插入计数器值到缓冲中然后重新设置response的头信息,最后把缓冲中的内容写入response。
PrintWriter out = response.getWriter();//构造包装类CharResponseWrapper wrapper = new CharResponseWrapper( (HttpServletResponse)response);//向 doFilter 传递包装类chain.doFilter(request, wrapper);CharArrayWriter caw = new CharArrayWriter();caw.write(wrapper.toString().substring(0, wrapper.toString().indexOf("</body>")-1));...caw.write("\n</body></html>");//重新设置响应长度response.setContentLength(caw.toString().length());out.write(caw.toString());out.close(); 6.5.1.3. 映射过滤器Web容器使用过滤器映射来决定是否过滤Web资源。在Web应用的部署描述文件中映射过滤器到Servlet或URL模板。
-
在部署描述文件中加入<filter>标记 ,此标记包括:
-
<filter-name>:过滤器名称
-
<filter-class>:过滤器的实现类
-
<init-params>:过滤器的初始参数
-
-
在部署描述文件中加入<filter-mapping>标记,映射过滤器到Servlet:
<filter-mapping> <filter-name>Compression Filter</filter-name> <servlet-name>CompressionTest</servlet-name> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher></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>映射过滤器到URL模板:
<filter> <filter-name>HitCounterFilter</filter-name> <filter-class>HitCounterFilter</filter-class></filter><filter-mapping> <filter-name>HitCounterFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher></filter-mapping>使用<url-pattern>/*<url-pattern>标记,此过滤器将使用于此web应用中的任何静态资源或Servlet内容,因为任何URL都可匹配"/*"的URL模式。
-
dispatcher元素的作用 :Servlet 2.4版的Web程序增强了filter和request dispatcher的配合功能,这样过滤器可以根据请求分发器(request dispatcher)所使用的方法有条件地对Web请求进行过滤。
-
只有当request直接来自客户,过滤器才生效,对应为REQUEST条件。
-
只有当request被一个请求分发器使用forward()方法转到一个Web构件时,对应称为FORWARD条件。
-
只有当request被一个请求分发器使用include()方法转到一个Web构件时,对应称为INCLUDE条件。
-
只有当request被一个请求分发器使用“错误信息页”机制方法转到一个Web构件时,对应称为ERROR条件。
-
第五种过滤器作用的条件可以是上面四种条件的组合。
-
当不使用dispatcher元素时,客户的直接request会被用来过滤请求。如果请求是从一个request dispatcher转发过来的,这个过滤器不工作。
-
如下图,可以映射一个过滤器到一个或多个Servlet,或者可以映射一个 Servlet到多个过滤器。
过滤器的映射
过滤器F1映射到servlet S1,S2和S3,过滤器F2映射到servlet S2,过滤器F3 映射到servlet S1和S2。
6.5.2. Application Events应用事件模型提供了当ServletContext,HttpSession,ServletRequest状态改变时的通知功能。可以编写事件监听类来响应这些状态的改变,并且可以配置和部署应用事件和监听类到Web应用。
对于ServletContext事件,当Web应用部署、卸载和对context增加属性时,事件监听类可以得到通知。下表列出了ServletContext的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 接口 方法 Servlet context被创建 javax.servlet.ServletContextListener contextInitialized() Servlet context被注销 javax.servlet.ServletContextListener contextDestroyed() 增加属性 javax.servlet. ServletContextAttributesListener attributeAdded() 删除属性 javax.servlet. ServletContextAttributesListener attributeRemoved() 属性被替换 javax.servlet. ServletContextAttributesListener attributeReplaced()对于HttpSession事件,当session激活、删除或者session属性的增加、删除和替换时,事件监听类得到通知。下表列出了HttpSession的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 接口 方法 session激活 javax.servlet.http. HttpSessionListener sessionCreated() session删除 javax.servlet.http. HttpSessionListener sessionDestroyed() 增加属性 javax.servlet.http. HttpSessionAttributesListener attributeAdded() 删除属性 javax.servlet.http. HttpSessionAttributesListener attributeRemoved() 属性被替换 javax.servlet.http. HttpSessionAttributesListener attributeReplaced()对于ServletRequest事件,当request初始化、销毁或者request属性的增加、删除和替换时,事件监听类得到通知。下表列出了ServletRequest的事件类型,对应特定事件的监听类必须实现的接口和当事件发生时调用的方法。
事件类型 接口 方法 session初始化 javax.servlet.ServletRequestListener requestInitialized() session销毁 javax.servlet.ServletRequestListener requestDestroyed() 增加属性 javax.servlet.ServletRequestAttributeListener attributeAdded() 删除属性 javax.servlet.ServletRequestAttributeListener attributeRemoved() 属性被替换 javax.servlet.ServletRequestAttributeListener attributeReplaced() 6.5.2.1. 配置事件监听类配置事件监听类的步骤:
-
打开Web应用的部署描述文件web.xml
-
增加事件声明标记<listener>。事件声明定义的事件监听类在事件发生时被调用。<listener>标记必须在<filter>和<filter-mapping>标记之后和<servlet>标记之前。可以为每种事件定义多个事件监听类,Apusic应用服务器按照它们在部署描述文件声明的顺序调用。例如:
<listener> <listener-class>myApp.myContextListenerClass</listener-class></listener><listener> <listener-class>myApp.mySessionAttributeListenerClass</listener-class></listener> -
编写和部署监听类。
编写事件监听类的步骤:
-
创建新的类并实现事件对应的接口
-
定义不接受参数、访问属性为public的构造函数
-
实现接口的方法
-
编译并拷贝到对应Web应用的WEB-INF/classes目录下,或者打包成jar文件拷贝到WEB-INF/lib目录下
ServletContext 监听类例子:
import javax.servlet.*;public final class myContextListenerClass implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { /* 当 ServletContext 初始化时被调用,可以在这儿 初始化 ServletContext 的相关数据 */ } public void contextDestroyed(ServletContextEvent event) { /* 当 Web 应用被卸载或 Apusic 服务器关闭时被调用 */ }}HttpSession 属性监听类例子:
import javax.servlet.*;public final class mySessionAttributeListenerClass implements HttpSessionAttributesListener { public void attributeAdded(HttpSessionBindingEvent sbe) { /* 增加session属性时被调用 */ } public void attributeRemoved(HttpSessionBindingEvent sbe) { /* 删除session属性时被调用 */ } public void attributeReplaced(HttpSessionBindingEvent sbe) { /* 替换session属性时被调用 */ }}ServletRequest 属性监听类例子:
import javax.servlet.*;public final class myRequestAttributeListenerClass implements ServletRequestAttributeListener { public void attributeAdded(HttpSessionBindingEvent sbe) { /* 增加request属性时被调用 */ } public void attributeRemoved(HttpSessionBindingEvent sbe) { /* 删除request属性时被调用 */ } public void attributeReplaced(HttpSessionBindingEvent sbe) { /* 替换request属性时被调用 */ }}-----------------------------------------------
-----------------------------------------------
Servlet和Filter的url匹配以及url-pattern详解Servlet和filter是J2EE开发中常用的技术,使用方便,配置简单,老少皆宜。估计大多数朋友都是直接配置用,也没有关心过具体的细节,今天遇到一个问题,上网查了servlet的规范才发现,servlet和filter中的url-pattern还是有一些文章在里面的,总结了一些东西,放出来供大家参考,以免遇到问题又要浪费时间。
一,servlet容器对url的匹配过程:当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是http://localhost/test/aaa.html,我的应用上下文是test,容器会将http://localhost/test去掉,剩下的/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的,而且当有一个servlet匹配成功以后,就不会去理会剩下的servlet了(filter不同,后文会提到)。其匹配规则和顺序如下:
1. 精确路径匹配。例子:比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为http://localhost/test ,这个时候容器就会先 进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的servlet了。
2. 最长路径匹配。例子:servletA的url-pattern为/test/*,而servletB的url-pattern为/test/a/*,此时访问http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB。
3. 扩展匹配,如果url最后一段包含扩展,容器将会根据扩展选择合适的servlet。例子:servletA的url-pattern:*.action
4. 如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet(什么是default servlet?后面会讲)。
根据这个规则表,就能很清楚的知道servlet的匹配过程,所以定义servlet的时候也要考虑url-pattern的写法,以免出错。
对于filter,不会像servlet那样只匹配一个servlet,因为filter的集合是一个链,所以只会有处理的顺序不同,而不会出现只选择一个filter。Filter的处理顺序和filter-mapping在web.xml中定义的顺序相同。
二,url-pattern详解在web.xml文件中,以下语法用于定义映射:
l 以”/’开头和以”/*”结尾的是用来做路径映射的。
l 以前缀”*.”开头的是用来做扩展映射的。
l “/” 是用来定义default servlet映射的。
l 剩下的都是用来定义详细映射的。比如: /aa/bb/cc.action
所以,为什么定义”/*.action”这样一个看起来很正常的匹配会错?因为这个匹配即属于路径映射,也属于扩展映射,导致容器无法判断。