第六章 过滤(JavaTM Servlet 规范3.1 )

Filtering 过滤

过滤器是 Java 组件,它允许动态改变进入资源的请求和从资源返回的响应的有效载荷(payload)和头(header)信息。

Java Servlet API 类和方法提供轻量框架过滤动态和静态内容。它描述了在Web应用中过滤器如何被配置和它们实现的约定和语义。

线上提供了 servlet 过滤器的 API 文档。在 14 章中的部署描述符概要给出了过滤器的配置语法。读者在阅读本章时应该使用这些资源作为参考。

6.1 What is a filter? 什么是过滤器

过滤器一坨可重用的代码,能转换 HTTP 请求,响应和头信息的内容。过滤器通常不产生响应或者像 servlet 那样对请求做出响应,而是修改或者调整对资源的请求,修改和调整来自资源的响应。

过滤器可以作用在动态或静态内容上。对本章来说,动态和静态内容指的是 Web 资源。

有以下几种功能性类型对需要使用过滤器的开发者可用:

▪   对资源的请求执行之前访问资源。


▪   对资源的请求之前对请求进行处理。


▪    通过对请求对象进行自定义版本包装来对请求头和数据进行修改。

▪    提供自定义版本的响应对象来修改响应头和响应数据。

▪    资源调用后对资源调用的拦截(?)。

▪    在一个 servlet,一组 servlet 或静态内容上按照指定的顺序执行0个,1个或多个过滤器。

6.1.1 Examples of Filtering Components 过滤组件的例子

▪    验证过滤器

▪    日志和审计过滤器

▪    图片转换过滤器

▪    数据压缩过滤器

▪    加密过滤器

▪    Tokenizing 过滤器

▪    触发资源访问事件过滤器

▪    转换 XML 内容的 XSL/T 过滤器

▪    MIME-type 链过滤器

▪    缓存过滤器

6.2 Main Concepts 主要概念

本节描述过滤模型的主要概念。

应用开发者通过实现 javax.servlet.Filter 接口并且提供一个无参公用构造器创建过滤器。此类跟构建 Web 应用的静态内容和 servlets 一起打包进 Web 归档中。过滤器在部署描述符中使用 <filter> 元素声明。一个过滤器或过滤器集合可以通过在部署描述符里定义 <filter-mapping> 元素来配置调用。通过映射 servlet 的逻辑名称把过滤器映射到一个特别的 servlet,通过映射一个 URL 模式将过滤器映射到一组 servlet 和静态内容资源来完成配置。

6.2.1 Filter Lifecycle 过滤器生命周期

Web应用部署之后,在请求导致容器访问Web资源之前,容器按照如下所述必须定位应用到Web资源的过滤器列表。容器必须保证列表中的每一个过滤器(元素)都实例化了一个适当类的过滤器(对象),然后调用它的 init(FilterConfig config) 方法。过滤器可能会抛出一个异常表明它不能正常运转。如果异常是 UnavailableException 类型,容器可以检查异常的 isPermanent 属性,并选择将来某个时候重试该过滤器。

在部署描述符中声明的每个 <filter> 在容器的每个 JVM 中只实例化一个实例。容器提供在过滤器部署描述符中声明的过滤器配置,Web应用的 ServletContext 的引用和一组初始化参数。

当容器接收到一个进入的请求,容器获取列表中的第一个过滤器的实例并且调用它的doFilter,传入 ServletRequest 和 ServletResponse,和 一个它将使用的 FilterChain 的引用。

过滤器的 doFilter 方法通常按照这个或者下面模式的子集来实现:

1. 该方法检查请求的头。

2. 为了修改请求头或数据,该方法可能使用 ServletRequest 或 HttpServletRequest 的自定义实现包装请求对象。

3. 该方法可能用 ServletResponse 或 HttpServletResponse 的自定义实现来包装响应对象,传入 doFilter 方法来修改响应头或数据。

4. 该过滤器可能会调用过滤器链中的下一个实体。下一个实体可能是另一个过滤器,或者如果执行调用的过滤器是此过滤器链中在部署描述符中为其配置的最后一个过滤器,下个实体就是目标Web资源。FilterChain 对象调用 doFilter 方法将影响下个实体的调用并传入它被调用时的 request 和 response 或者传入它可能已创建的包装版本。过滤器链的 doFilter 方法的实现,由容器提供,必须定位过滤器链中的下一个实体并且调用它的 doFilter 方法,传入恰当的 request 和 response 对象。或者,过滤器链可以通过不调用下一个实体阻塞请求,由离开的过滤器负责填充响应对象。service 方法必须与应用到 servlet 的所有过滤器运行在相同的线程中。

5. 链中的下一个过滤器调用之后,该过滤器可能检查响应的头。

6. 另外,过滤器可以抛出一个异常表明一个错误正在处理。如果过滤器在其 doFilter 处理中抛出了一个 UnavailableException,容器不应该尝试继续处理剩下的过滤器链,如果异常没有标识为永久的,它可能选择晚些时候重试整个过滤器链。

7.当链中的最后一个过滤器被调用,下一个访问的实体是目标 servlet 或者位于链尾的资源。

8.在一个过滤器实例可以被容器从服务中移除之前,容器必须首先调用过滤器的 destroy 方法使过滤器释放所有资源和执行其他清除操作。

6.2.2 Wrapping Requests and Responses 包装请求和响应

过滤的核心概念是包装请求和响应以便它能覆盖行为执行过滤任务。在此模型中,开发者不仅有能力覆盖请求和响应对象已存在的方法,也能提供新的API对链中剩余的过滤器或目标 web 资源执行特别过滤任务。例如,开发者可能希望使用更高级输出对象(output stream 或 wirter)来扩展响应对象,比如允许DOM对象被写回客户端的API。

为了支持这种风格的过滤器,容器必须满足下面的要求。当过滤器【1】调用容器的过滤器链实现的 doFilter 方法时,容器必须确保传递给过滤器链的下一个实体或目标 web 资源(如果此过滤器是链中的最后一个)的 request 和 response 对象与传入调用过滤器(指【1】)的 doFitler 方法的(request 和 response)对象相同。

当调用者包装 request 或 response 对象时,对包装对象标识的要求同样适用于从 servlet 或 filter 到 RequestDispatcher.forward 或 RequestDispatcher.include 的调用。

6.2.3 Filter Environment 过滤器环境

通过在部署描述符中使用 <init-params> 元素可以将一组初始化参数与过滤器关联。通过过滤器的 FilterConfig 对象的 getInitParameter 和 getInitParameterNames 方法,过滤器可以在运行时使用这些参数的名称和值。除此之外,FilterConfig 提供对 Web 应用的 ServletContext 的访问来加载资源、记录日志和在 ServletContext 的属性列表中存储状态。。

6.2.4 Configuration of Filters in a Web Application Web应用中过滤器的配置

过滤器既可以通过在8.1.2节中定义的 @WebFilter 注解来定义,也可以通过在部署描述符中使用  <filter> 来定义。在此元素中,程序员可以作以下声明:

■ filter-name: 用来映射过滤器到 servlet 或 URL。

■ filter-class:容器用来识别过滤器类型。

■ init-params:过滤器的初始化参数。

程序员可以有选择的为过滤器指定 icons,文本描述和一个为了工具操作的显示名称。容器必须为在部署描述中声明的每一个过滤器准确实例化一个定义该过滤器的 Java 类的实例。因此,如果开发者为同一个过滤器类做了两个过滤器声明,容器将实例化两个相同过滤器类的实例。

这里有一个过滤器声明的例子:

<filter>

    <filter-name>Image Filter</filter-name>

    <filter-class>com.acme.ImageServlet</filter-class>

</filter>

一旦过滤器在部署描述中声明,组装者使用  <filter-mapping> 元素定义此过滤器将应用在此 Web 应用中的哪些 servlets 和静态资源上。使用 <servlet-name> 元素,过滤器可以关联一个 servlet。例如:下面代码示例映射 Image Filter 过滤器到 ImageServlet servlet:

<filter-mapping>


<filter-name>Image Filter</filter-name>

<servlet-name>ImageServlet</servlet-name>

</filter-mapping>

使用<url-pattern>风格的过滤器映射,过滤器可以关联一组 servlets 和静态内容:

<filter-mapping>


<filter-name>Logging Filter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

在此,Logging 过滤器被应用到Web应用中所有的 servlets 和静态内容页面,因为每个请求 URI 都匹配 ‘/*’ URL 模式。

当使用 <url-pattern> 风格处理 <filter-mapping> 元素时,容器必须使用在12章定义的路径映射规则“映射请求到Servlets”来确定此 <url-pattern> 是否匹配请求URI。

容器为特定请求URI创建应用到它的过滤器链的顺序如下:

1. 首先,按照这些元素在部署描述符中出现的顺序,<url-pattern> 匹配过滤器映射。

2.接下来:按照这些元素在部署描述符中出现的顺序,<servlet-name> 匹配过滤器映射。(注:间接通过 <servlet-mapping> 元素的 <url-pattern>?)

如果一个过滤器映射同时包含<servlet-name> 和 <url-pattern>,容器必须展开此过滤器映射为多个过滤器映射(每个 <servlet- name> 和 <url-pattern> 一个映射),保留 <servlet-name> 和 <url-pattern> 元素的顺序。例如下面的过滤器映射:

<filter-mapping>

    <filter-name>Multipe Mappings Filter</filter-name>

    <url-pattern>/foo/*</url-pattern>

    <servlet-name>Servlet1</servlet-name>

    <servlet-name>Servlet2</servlet-name>

    <url-pattern>/bar/*</url-pattern>

</filter-mapping>

 

等价于:

<filter-mapping>

    <filter-name>Multipe Mappings Filter</filter-name>

    <url-pattern>/foo/*</url-pattern>

</filter-mapping>

<filter-mapping>

    <filter-name>Multipe Mappings Filter</filter-name>

    <servlet-name>Servlet1</servlet-name>

</filter-mapping>

<filter-mapping>

    <filter-name>Multipe Mappings Filter</filter-name>

    <servlet-name>Servlet2</servlet-name>

</filter-mapping>

<filter-mapping>

    <filter-name>Multipe Mappings Filter</filter-name>

    <url-pattern>/bar/*</url-pattern>

</filter-mapping>

 

关于过滤器链的顺序要求意味着,当接收到一个进入的请求时,容器如下处理请求:

▪   根据第122页的“映射规范”的规则识别目标Web资源。

▪   如果有通过 servlet 名称匹配的过滤器并且此 Web 资源有一个<servlet-name>,容器按照部署描述符中声明的顺序构建过滤器匹配链。链中的最后一个过滤器对应着最后<servlet-name>匹配的过滤器并且由它调用目标Web资源。

▪   如果有使用<url-pattern>匹配的过滤器,并且 <url-pattern>按照 12.2节“映射规范”中的规则匹配请求URI,容器按照在部署描述符中声明的顺序来创建<url-pattern>匹配过滤器链。此链中最后过滤器是在部署描述中与请求URI最后<url-pattern>匹配的过滤器。此链中的最后过滤器调用<servlet-name>匹配链中的第一个过滤器,如果没有的话调用目标Web资源。可以想象高性能Web容器将缓存过滤器链而不必基于每个请求计算他们。

6.2.5 Filters and the RequestDispatcher
过滤器和请求分派器(RequestDispatcher)

从 Java Servlet 规范新版本 2.4 起可以配置请求分派器 forward() 和 include() 调用时被调用的过滤器。在部署描述符中使用新的 <dispatcher> 元素,开发者可以为 filter-mapping 指明他希望此拦截器应用到的请求:

1. 直接来自客户端的请求。可以由带有 REQUEST 值的 <dispatcher> 元素或者没有任何 <dispatcher> 元素表示。

2. 使用表示[匹配  <url-pattern> 或 <servlet-name>的Web组件] 的请求分派器的 forward() 调用下处理的请求。用一个带有值 FORWARD 的 <dispatcher> 元素表示。

3. 使用表示[匹配  <url-pattern> 或 <servlet-name>的Web组件] 的请求分派器的 include() 调用下处理的请求。用一个带有值 INCLUDE 的 <dispatcher> 元素表示。

4. 对因匹配 <url-pattern> 的错误资源而使用 108 页“错误处理”中制定的错误页面机制处理的请求。用一个带有值 ERROR 的 <dispatcher> 元素表示。

5. 
使用第10页“异步处理”中制定的异步上下文分派机制对[ web 组件使用 dispatch 调用]处理的请求。用一个带有值 ASYNC 的 <dispatcher> 元素表示。

6. 或者上面 1,2,3,4 或 5的任何组合。

例如:

<filter-mapping>

       <filter-name>Logging Filter</filter-name>

       <url-pattern>/products/*</url-pattern>

</filter-mapping>

以 /products/...开始的客户端请求将导致 Logging Filter 被调用,但在以路径开头为/products/...的请求分派器的请求分派调用时不会。LoggingFilter 在请求的初始分派和恢复请求时都会被调用。

下面的代码:

<filter-mapping>

    <filter-name>Logging Filter</filter-name>

    <servlet-name>ProductServlet</servlet-name>

    <dispatcher>INCLUDE</dispatcher>

</filter-mapping>

客户端对 ProductServlet 的请求和请求分派器 forward() 调用到 ProductServlet 时不会导致 Logging Filter 被调用,但是以 ProductServlet 开始的名字的请求分派器 include() 调用时会调用它。

下面的代码:

<filter-mapping>

    <filter-name>Logging Filter</filter-name>

    <url-pattern>/products/*</url-pattern>

    <dispatcher>FORWARD</dispatcher>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

以 /products/...开始的客户端请求和路径开头为/products/... 的请求分派器在 forward() 调用时会导致 Logging Filter 被调用。

最后,下面的代码使用了特殊的 servlet 名字 ‘*’:

<filter-mapping>

    <filter-name>All Dispatch Filter</filter-name>

    <servlet-name>*</servlet-name>

    <dispatcher>FORWARD</dispatcher>

</filter-mapping>

按照名字或路径获得的所有请求分派器 forward() 调用时,这些代码会导致“All Dispatch Filter”被调用。

posted on 2016-07-20 14:52  whilliy  阅读(204)  评论(0编辑  收藏  举报