Servlet 3.0 之 Filtering

Filters是Java组件,它们在从请求到资源及从资源到响应上允许有效负荷与头部信息的传递。

本章描述了Java Servlet v.3.0中提供为行为和静态内容过滤的轻量级框架的API类和方法。它描述了filter在一个Web应用中如何被配置以及它们的实现的约定和机制。

Servlet filters有在线API文档。filters的配置语法在14章的部署描述符Deployment Descriptor机制中有具体描述。读者在阅读本章的时候应该使用这些资源作为参考。

什么是filter

filter是一个可重用代码片段,它能变换HTTP request,response,以及头部信息的内容。Filters通常不会创建一个response,或者如servlet去响应一个请求,而是为一个资源修改或者调整请求,从资源中修改或者调整响应。

Filters能作用于动态或者静态内容。为了本章的目的,动态和静态内容统称为Web资源。

对需要使用filters的开发者,可用的功能如下:

  • 请求调用之前访问资源。
  • 被调用前处理对资源的请求。
  • 通过把请求对象包装成定制版本来修改请求头部和数据。
  • 通过把响应对象包装成定制版本来修改响应头部和响应数据。
  • 在它调用后拦截对一个资源调用。
  • 按指定的顺序作用于一个servlet,一组servlet,或者0个,一个或多个filter上。

拦截器组件的例子

  • 鉴权filters
  • 日志和审计filters
  • 图像转换filters
  • 数据压缩filters
  • 加密filters
  • 标记filters
  • 触发资源访问事件
  • 转换XML内容的XSL/T filters
  • MIME-type链filters
  • 缓存filters

主要概念

应用开发者通过实现javax.servlet.Filter 接口且提供一个无参public类型的构造函数来创建了一个拦截器。这个类与静态内容和组成Web应用的servlets一起打包在Web归档文件中。一个filter用<filter>元素在部署描述符中声明。一个filter或者filter集合能够通过在部署描述符中定义<filter-mapping>元素来配置来被调用。这通过按照servlet的逻辑名把filter映射到指定的servlet,或者通过映射一个filter到一个URL pattern来把filter映射到一组servlet和静态资源。

Filter 生命周期

在Web应用部署以后,并且请求引发容器访问Web资源之前,容器必须定位应用于上述Web资源的拦截器列表。容器必须确保为列表中每一个filter的类实例化一个拦截器,并且调用它的init(FilterConfig config) 方法。这个filter可以抛出一个异常来表明它不能正常工作。如果异常是UnavailableException类型,容器可以检查异常的isPermanent属性并且可以选择稍后重试filter。

对于一个容器的每个JVM中,部署描述符中每个<filter>声明仅有一个实例会被实例化。容器提供一个过滤器config,它指向Web应用中ServletContext的引用,以及初始化参数的集合一样。

当容器接收到一个传入的请求,它会使用列表中第一个过滤器实例,并且调用doFilter方法,并传递ServletRequest,ServletResponse以及FilterChain对象的一个引用给这个方法。

一个过滤器的doFilter 方法将依据下列模式或者下列模式的子集来实现:

  1. 方法检查请求的头部。
  2. 为了修改请求的头部或者数据,方法可以用ServletRequest或者HttpServletRequest的定制实现来包装请求对象。
  3. 方法可以用ServletResponse或者HttpServletResponse的一个实现来包装响应对象,传递给它的doFilter方法来修改响应头或者数据。
  4. 过滤器可以调用过滤器链中下一个entity。下一个实体可以是另一个过滤器,或者如果发起调用的过滤器是部署描述符中配置的最后一个过滤器,下一个实体就是目标Web资源。下一个实体的调用通过调用FilterChain对象上的doFilter方法生效,同时把request和response对象或者它们的包装对象传递给doFilter方法。
    另外,过滤器链可以通过不调用下一个实体来阻塞request,让filter来填充响应对象。
  5. 在调用链中下一个过滤器之后,过滤器可以检查响应头。
  6. 另外,过滤器可以抛出一个异常来表明处理过程中的一个错误。如果在它的doFilter处理过程中抛出UnavailableException异常,容器一定不能尝试继续执行过滤器链。如果异常没有被标识永久,容器可以选择稍后重试整个链。
  7. 当链中的最后一个过滤器被调用,访问的下一个实体将是链尾处的目标servlet或者资源。
  8. 在一个过滤器实例能被容器从service中移除之前,容器必须首先调用过滤器上的destroy方法来使filter释放任何资源以及执行一些其它清理动作。

包装Requests和Responses

过滤器概念的核心是包装一个request或者response,目的是重写行为来执行一个过滤任务。在这个模型中,开发者不仅能够重写request和response中已存在的方法,而且为执行链中的过滤器或者目标web资源提供用于特定过滤任务的新API。比如,开发者可能希望用更高级的输出对象(i.e. 输出流或者writer)来扩展响应对象,这些API能够让DOM对象被写回客户端。

为了支持这种类型的过滤器,容器必须支持下列要求。当一个过滤器调用容器的过滤器链实现上的doFilter方法,容器必须保证它传递给执行链中的下一个实体,或者当这个过滤器是当前链中最后一个时就传递给目标Web资源的request和response对象与调用过滤器时传递进doFilter方法的对象是同一个对象。

当调用者包装了request或者response对象,包装对象的相同要求适用于来自一个servlet或者对RequestDispatcher.forward or RequestDispatcher.include的过滤器的调用。这种场景下,发起调用的servlet的request和response对象必须与调用servlet或filter时传递进来的是相同的包装对象。

Filter 环境

一个初始化参数集合可以在部署描述符中使用<init-params>元素与filter关联起来。这些参数的名字和值能够在运行时通过过滤器的FilterConfig对象上的getInitParametergetInitParameterNames方法让filter访问到。此外,为了加载资源,记录日志,以及在ServletContext的属性列表中保存状态,FilterConfig能够访问Web应用的ServletContext。一个Filter和一个过滤器链尾的目标servlet或者资源必须在一个调用线程中执行。

在Web应用中配置Filters

一个filter通过@WebFilter注解或者在部署描述符中使用<filter>元素来定义。这个元素中,程序员声明下列元素:

  • filter-name: 把filter映射到一个servlet或URL
  • filter-class: 容器用它来识别filter类型
  • init-params: 给filter配置初始化参数

程序员能为工具操作指定icons,文本描述以及显示名字。容器必须准确地为部署描述符中每一个filter声明实例化一个定义filter的Java类实例。因此,如果开发者为同一个filter类声明了两次,容器为同一个filter类实例化两个实例。

这是一个filter声明的例子:

<filter>
  <filter-name>Image Filter</filter-name>
  <filter-class>com.acme.ImageServlet</filter-class>
</filtler>

一旦一个filter在部署描述符中声明,assembler用<filter-mapping>元素来定义Web应用中filter对应的servlets和静态资源。Filters能使用<servlet-name>元素与一个servlet关联起来。比如,下列代码把Image Filter filter映射到ImageServletservlet:

<fillter-mapping>
  <filter-name>Image Filter</filter-name>
  <servlet-name>ImageServlet</servlet-name>
</fillter-mapping>

Filters通过使用<url-pattern>filter映射方式与一组servlet和静态内容关联:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

这是应用于Web应用中所有servlets和静态内容页面的日志过滤器,因为每一个请求URI都匹配'/*'URL模式。
当使用<url-pattern>方式处理一个<filter-mapping>元素,容器必须使用路径匹配规则来决定<url-pattern>是否匹配请求URI。

在应用于某个特定请求URI的filter链中,容器的使用顺序如下:

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

如果一个过滤器映射同时包含<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>

当接受到一个进来的请求,filter链的顺序的要求容器按如下步骤处理请求:

  • 根据映射规范的规则,识别目标Web资源。
  • 如果有通过servlet名字匹配的过滤器,以及Web资源有一个<servlet-name>,容器会根据部署描述符中声明的顺序建立过滤器链。链中最后一个filter对应于过滤器匹配的最后一个<servlet-name>,并且最后一个filter是调用目标Web资源的filter。
  • 如果有使用<url-pattern>的过滤器匹配filter,以及<url-pattern>根据映射规范规则匹配请求URI,容器按照部署描述符中声明的顺序构建匹配<url-pattern>的过滤器链。链中的最后一个过滤器是部署描述符中最后一个匹配<url-pattern>的filter。“链中最后一个过滤器是<servlet-name>匹配链中调用第一个过滤器的过滤器,或者如果没有<servet-name>,哪儿调用目标Web资源。”

高性能Web容器将缓存过滤器链,这样它们就不需要对每一个请求推断过滤器链。

Filters 和 RequestDispatcher

自Java Servlet 2.4 规范起,可以在请求分发forward()include()方法中来配置被调用的filter。

通过在部署描述符中使用新的<dispatcher>元素,开发者能够为一个filter-mapping表明,他是否想把这个filter应用于请求:

  1. 请求直接来自客户端。
    这通过一个带REQUEST值的<dispatcher>元素标识符,或者缺少任何<dispatcher>元素来标识。
  2. 请求在一个使用forward()方法匹配<url-pattern>或<servlet-name>,代表Web组件的请求分发器下被处理。
    这通过带值FORWARD的<dispatcher>元素。
  3. 请求在一个使用include()方法匹配<url-pattern>或<servlet-name>,代表Web组件的请求分发器下被处理。
    这通过带值INCLUDE的<dispatcher>元素。
  4. 请求正在用错误页面机制跳转到匹配<url-pattern>的错误资源。
  5. 请求正在用异步上下文分发机制跳转到使用一个使用dispatch方法的Web组件。
    这通过带ASYNC值的<dispatcher>元素标识。
  6. 或者上述条件的任意组合。

比如:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
</filter-mapping>

这会让Logging Filter被/products/...开头的客户端请求调用,但是不会在一个有以/products/...开头路径的请求分发器调用下被调用。LoggingFilter 将会在请求调度开始时以及恢复的请求上被调用。下列代码:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <servlet-name>ProductServlet</servlet-name>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

这会让Logging Filter不能被请求ProductServlet的客户端调用,也不会被请求分发器forward()调用到,但是会在一个请求分发器include()方法中被调用。下列代码:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

这会让Logging Filter被以/products/..开头的客户端请求,以及在一个以ProductServlet开头的请求分发器的请求分发器forward()方法下调用。下列代码:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

这会让Logging Filter被以/products/..开头的客户端请求以及在一个请求分发器有/products/..开头的路径的请求分发forward()方法下调用。

最后,下列代码使用特殊的servlet名字'*':

<filter-mapping>
  <filter-name>All Dispatch Filter</filter-name>
  <servlet-name>*</servlet-name>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>

这段代码会让所有的分发过滤器在请求分发forward()方法上被调用。

翻译自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容