天天看点

驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

早在2001年6月,我写了一篇文章,预览了当时称为过滤的全新Servlet 2.3功能。 Tomcat 4.x服务器支持此功能,并且过滤器使Web应用程序开发人员能够创建灵活的应用程序组件,可以在处理实际请求之前和之后将其插入并链接到应用程序服务器的请求流中。 而且由于过滤器被打包为应用程序级组件,与JSP页面和Servlet一样,因此可以轻松地将它们捆绑在同一WAR文件中,以部署在任何符合Servlet 2.3的服务器上。 从那时起,主要的应用程序服务器供应商就接受Servlet 2.3标准来支持过滤,并且过滤器已成为许多Web应用程序的基础组件。

Servlet 2.4过滤器增强

基于Web开发社区的实际“现场”反馈,Servlet 2.4规范(现已定稿)为过滤器提供了一项非常重要的新功能。 这项新功能最终使过滤器可以与servlet和JSP页面完全地协同工作,使Web应用程序设计人员可以使用过滤器添加主要的应用程序功能。 这项新功能还促进了一种流行的Web应用程序设计新样式,称为JSP Model 2或模型视图控制器 (MVC)Web应用程序设计。 (请参阅相关主题关于JSP的Model 2或MVC风格的Web应用程序设计的更多信息。)

过滤请求分配器的处理流程

Servlet 2.4中的新过滤功能从根本上减轻了过滤器只能在应用程序服务器实际处理请求之前和之后在请求流中运行的限制。 取而代之的是,Servlet 2.4过滤器现在可以在每个分配点与请求分配器进行交互。 这意味着当Web资源将请求转发到另一个资源(例如,将请求转发到同一应用程序中的JSP页面的servlet)时,可以在目标资源处理请求之前运行过滤器。 这也意味着,如果Web资源包括其他Web资源的输出或功能(例如,一个JSP页面包括多个其他JSP页面的输出),则Servlet 2.4过滤器可以在每个包含的资源之前和之后工作。 图1说明了Servlet 2.3和Servlet 2.4的调度程序交互之间的区别。

图1. Servlet 2.3和Servlet 2.4过滤器的区别
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

控制过滤器调度程序的交互

筛选器与容器的请求处理流的交互是通过部署描述符(web.xml文件)中的

<filters-mapping>

元素定义来配置的。 这是Servlet 2.3规范中建立的约定,在Servlet 2.4中未更改。 相反,通过新的可选

<dispatcher>

子元素控制与调度程序交互的新功能。 清单1显示了部署描述符中的

<filters-mapping>

元素,该元素使用

<dispatcher>

子元素将名为“ Blue Filter”的过滤器应用于可能包含的任何JSP页面(假设应用程序中的所有JSP页面映射到

/jsp/*

URL模式):

清单1. Servlet 2.4样式的过滤器映射
<filter-mapping>
           <filter-name>Blue Filter</filter-name>
           <url-pattern>/jsp/*</url-pattern>
           <dispatcher>INCLUDE</dispatcher>
 </filter-mapping>
           

要将相同的过滤器应用于调度程序转发的资源以及包含的资源,我们只需添加另一个

<dispatcher>

子元素,如清单2所示:

清单2.向前映射并包括
<filter-mapping>
           <filter-name>Blue Filter</filter-name>
           <url-pattern>/jsp/*</url-pattern>
           <dispatcher>INCLUDE</dispatcher>
           <dispatcher>FORWARD</dispatcher>
 </filter-mapping>
           

表1中显示了

<dispatcher>

子元素可以包含的值:

表1. <dispatcher>子元素的可能值
描述
请求 将过滤器应用于来自客户端的请求-与Servlet 2.3兼容容器中的默认行为完全相同。
前锋 在Web资源之间使用

Dispatcher.forward()

调用将过滤器应用于请求进行转发时。
包括 当通过

Dispatcher.include()

调用包含Web资源时,将过滤器应用于请求。
错误 将过滤器应用于错误处理资源(本质上是发生错误时由容器管理的转发机制)。

当然,表1中的所有值仍受

<filter-mapping>

<url-pattern>

子元素指定的匹配。 本质上,只有与

<url-pattern>

<dispatcher>

子元素匹配的

<url-pattern>

才会被过滤。

为了更好地理解新过滤器/调度程序交互的操作,让我们将一些过滤器在Tomcat服务器中工作。

下载并安装Tomcat 5 plus JSTL

所有代码均已在Tomcat 5.0 alpha下进行了全面测试,该版本是在撰写本文时提供的最新版本。 除了安装Tomcat外,您还需要下载JSTL 1.0二进制文件。 将standard.jar和jstl.jar放入webapps / dvworks / web-inf / lib目录中,并将所有TLD文件放在webapps / dvworks / web-inf目录下。 (请参阅相关主题有关安装Tomcat和下载JSTL 1.0二进制文件的详细信息)。)

配置Tomcat 5过滤器

假设您已经安装了Tomcat 5并与JSP标准标记库1.0一起运行(请参见侧栏“下载并安装Tomcat 5和JSTL”),我们现在可以使用一些实际的过滤器示例。 下载源代码以获取示例Web应用程序。

让我们看一下几个JSP页面和过滤器的编码。 我们最初将使用的资源是:

  • /jsp/filtercount.jsp
  • /jsp/included.jsp
  • AddAttributesFilter
  • RedFilter

filtercount.jsp资源非常简单,由清单3所示的代码组成:

清单3. filtercount.jsp的代码
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<HTML>
<HEAD>
</HEAD>
<BODY>
<H1>developerWorks Tomcat 5 Filters</H1>

<jsp:include page="/jsp/included.jsp">
  <jsp:param name="BoxColor" value="red"/>
</jsp:include>

<br/>
<FONT  SIZE="4">
<c:if test="${(requestScope.BlueCounter != null) && 
    (requestScope.BlueCounter > 0)}">
  <font color="blue">The BlueFilter has been activated 
      ${requestScope.BlueCounter} times.</font></br>
</c:if>
<c:if test="${(requestScope.RedCounter != null) && 
    (requestScope.RedCounter > 0)}">
  <font color="red">The RedFilter has been activated 
      ${requestScope.RedCounter} times.</font></br>
</c:if>
</FONT>
</BODY>
</HTML>
           

该JSP页面调用调度程序以包含另一个名为

/jsp/included.jsp

JSP页面。 它还使用JSTL表达式语言打印出两个计数器(

RedCounter

BlueCounter

)的值,这些计数器作为属性附加到传入请求中(如果存在)并且计数大于零。

实际上,每个计数器都用于计算在请求处理期间

RedCounter

BlueCounter

被调用的次数。 表2显示了我们将使用的过滤器及其实际作用。

表2.实验方案中过滤器的名称和功能
筛选器名称 描述

AddAttributesFilter

BlueCounter

RedCounter

属性添加到传入请求。

RedFilter

增加附加到请求的

RedCounter

属性的值。

BlueFilter

增加附加到请求的

BlueCounter

属性的值。

表2中的所有过滤器都是

com.ibm.dvworks.filters

软件包的一部分。

清单4中显示了

com.ibm.dvworks.filters.AddAttributesFilter

的代码。请注意,它只是将两个计数器属性创建为

java.lang.Integer

类型,其值为0。

清单4. AddAttributesFilter.java的代码
package com.ibm.devworks.filters;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;


public final class AddAttributesFilter implements Filter {

    public static final String RED_COUNTER_ATTRIB = "RedCounter";
    public static final String BLUE_COUNTER_ATTRIB = "BlueCounter";


    private FilterConfig filterConfig = null;

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
	throws IOException, ServletException {

        request.setAttribute(RED_COUNTER_ATTRIB, new Integer(0));
        request.setAttribute(BLUE_COUNTER_ATTRIB, new Integer(0));
        filterConfig.getServletContext().log("Inside AddAttributesFilter");
        chain.doFilter(request, response);

    }


    public void destroy() {
        this.filterConfig = null;
    }


    public void init(FilterConfig filterConfig) {
	this.filterConfig = filterConfig;

    }

}
           

该滤波器的构造遵循基本的滤波器编码模式。

类似地,

com.ibm.dvworks.filters.RedFilter

的代码如清单5所示。请注意,该代码只是查找

RedCounter

属性,并在找到该属性时对其进行递增。

清单5. RedFilter.java的代码
package com.ibm.devworks.filters;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;


public final class RedFilter implements Filter {

    public static final String COUNTER_ATTRIB = "RedCounter";
    private FilterConfig filterConfig = null;

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
	throws IOException, ServletException {

    filterConfig.getServletContext().log("Inside RedFilter");
    Integer curCount = (Integer) request.getAttribute(COUNTER_ATTRIB);
    if (curCount != null)
        request.setAttribute(COUNTER_ATTRIB, 
            new Integer(curCount.intValue() + 1));

      chain.doFilter(request, response);

    }


    public void destroy() {
        this.filterConfig = null;
    }


    public void init(FilterConfig filterConfig) {
	this.filterConfig = filterConfig;

    }

}
           

com.ibm.dvworks.filters.BlueFilter

类具有相同的代码来

RedFilter

,除了恒定

COUNTER_ATTRIB

被定义为

BlueFilter

,如清单6所示:

清单6. BlueFilter的代码更改
public static final String COUNTER_ATTRIB = "BlueCounter";
           

filtercount.jsp文件使用调度程序包括included.jsp文件。 included.jsp文件仅创建一个表,该表填充了由称为

BoxColor

的输入参数指定的颜色。 请注意,在表中呈现颜色时使用了表达语言(EL),如清单7所示:

清单7. included.jsp的代码
<table  bgcolor="${param.BoxColor}" cellpadding="5">
      <tr> 
      <td>this page is included in-line using dispatcher</td>
      </tr>
</table>
           

通过这些过滤器和JSP页面,我们准备开始探索五个不同的过滤器配置方案。

场景1:标准Servlet 2.3过滤器交互-仅限REQUEST

在第一种情况下,我们将配置过滤器,使其以较早的Servlet 2.3容器支持的方式工作。

清单8显示了我们将使用的

<filter-mapping>

元素:

清单8.用于标准Servlet 2.3兼容性的过滤器映射
<filter-mapping>
  <filter-name>Add Attributes Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>Red Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
</filter-mapping>
           

编辑您的web.xml以反映清单8的配置。 在这里,我们没有指定

<dispatcher>

子元素。 这意味着将像在Servlet 2.3容器中一样,仅在REQUEST的基础上应用过滤器。 清单8中的配置在操作上与清单9中的代码相同:

清单9.等效的Servlet 2.4过滤器映射
<filter-mapping>
  <filter-name>Add Attributes Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
  <filter-name>Red Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>
           

启动Tomcat 5,并使用

http://<hostname>:8080/dvworks/jsp/filtercount.jsp

URL访问Web应用程序。 图2显示了filtercount.jsp生成的响应:

图2. Servlet 2.3兼容的REQUEST过滤
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

按照filtercount.jsp响应,

RedFilter

被称为只有一次遇到这种情况。 图3是详细的交互图,显示了通过请求处理器的请求流:

图3. Servlet 2.3过滤请求流
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

在图3中,过滤器链仅被调用一次-当请求从客户端进入系统时。 过滤器链当然由

AddAttributesFilter

RedFilter

组成,如图4所示:

图4.过滤器链的详细信息
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

在此Servlet 2.3样式的配置中,

RedFilter

仅在直接来自客户端的传入请求上应用一次。

方案2:结合REQUEST和INCLUDE交互

我们将用于此方案中的过滤器映射清单10.注意使用了Servlet 2.4语法和添加的中示出INCLUDE调度子元件

RedFilter

清单10.组合的REQUEST和INCLUDE交互的过滤器映射
<filter-mapping>
  <filter-name>Add Attributes Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
  <filter-name>Red Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>
           

重新启动Tomcat 5,并使用

http://<hostname>:8080/dvworks/jsp/filtercount.jsp

URL再次访问该应用程序。 图5所示的响应显示了过滤器计数。

图5.请求和包含过滤
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

现在

RedFilter

已被调用两次-一次在传入请求中,第二次包含include.jsp。 这两个调用在图6所示的交互图中进行了详细说明。请注意,我们通过仅显示请求进入系统的流程(而不是像以前那样进行的详细返回流程)简化了该图。

图6.组合的REQUEST和INCLUDE交互
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

方案3:结合REQUEST,INLUDE和FORWARD交互

接下来,我们将引入另一个简单的JSP页面,称为forwarder.jsp,如清单11所示。它仅使用分派器将传入的请求转发到filtercount.jsp。

清单11. forwarder.jsp的代码
<jsp:forward page="/jsp/filtercount.jsp"/>
           

现在,我们可以将

BlueFilter

添加到部署描述符中,如清单12所示:

清单12.组合​​的REQUEST,INCLUDE和FORWARD交互的过滤器映射
<filter-mapping>
  <filter-name>Add Attributes Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
  <filter-name>Red Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<filter-mapping>
  <filter-name>Blue Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>
           

BlueFilter

配置为仅处理转发的请求,而其余过滤器映射保持不变。

重新启动Tomcat 5,并使用

http://<hostname>:8080/dvworks/jsp/forwarder.jsp

URL访问该应用程序。 图7显示了响应。

图7. REQUEST,INLUDE和FORWARD过滤
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

在这种情况下,我们看到

BlueFilter

实际上被调用了一次-在将请求从forwarder.jsp转发到filtercount.jsp的过程中。 图8中的交互图显示了该流程。

图8.组合的REQUEST,INCLUDE和FORWARD交互
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

场景4:将REQUEST,INLUDE,FORWARD和旧版Servlet 2.3交互结合在一起

在这种情况下,我们将添加上一篇过滤文章中的过滤器。 您可以使用

ReplaceTextFilter

替换响应中的文本。 我们使用清单13所示的

<filter>

定义中的初始参数来配置它:

清单13.带有初始化参数的ReplaceTextFilter的过滤器定义
<filter>
        <filter-name>Replace Text Filter</filter-name>
        <filter-class>com.ibm.devworks.filters.ReplaceTextFilter
        </filter-class>
         <init-param>
	    <param-name>search</param-name>
	    <param-value>color="red"</param-value>
	  </init-param>
        <init-param>
	    <param-name>replace</param-name>
	    <param-value>color="green"</param-value>
	  </init-param>
    </filter>
           

然后,将以下

<filter-mapping>

到上一场景中使用的内容中,如清单14所示:

清单14.用于合并的REQUEST,INCLUDE,FORWARD和旧版Servlet 2.3过滤器交互的过滤器映射
<filter-mapping>
  <filter-name>Replace Text Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
</filter-mapping>
...
           

请注意,我们此处未指定

<dispatcher>

子元素,因此此过滤器默认为仅REQUEST处理。

重新启动Tomcat 5并访问

http://<hostname>:8080/dvworks/jsp/forwarder.jsp

URL。 您应该看到类似于图9的内容。与以前的配置不同,现在包含的框为绿色。

图9.组合的REQUEST,INLUDE,FORWARD和遗留过滤
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

图10所示的交互图说明了

ReplaceTextFilter

在传递回客户端之前如何处理响应以进行最终处理。 在这里,表格的颜色从红色变为绿色。

图10.组合的REQUEST,INLUDE,FORWARD和遗留交互
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

Servlet 2.4容器与Servlet 2.3过滤器,定义和映射完全向后兼容。

方案5:筛选错误分配

对于最后一种情况,我们将在分派到错误处理页面时简要检查过滤器的应用。 清单15中是我们将使用的过滤器映射。请确保删除web.xml中的所有其他过滤器映射。

清单15.过滤错误
<filter-mapping>
  <filter-name>Red Filter</filter-name>
  <url-pattern>/jsp/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
  <dispatcher>ERROR</dispatcher>
</filter-mapping>
           

为了有目的地产生错误,我们创建了一个JSP页面,称为errorgen.jsp,该页面设置了一个名为errorhdlr.jsp的页面来处理错误,然后引发异常,如清单16所示:

清单16. errorgen.jsp的代码
<%@page errorPage="/jsp/errorhdlr.jsp" %>

<% 
    if (true)
      throw (new Exception("Purposely generated exception!")); 
%>
           

当页面捕获错误时,容器会将请求转发到

@page

指令中指定的

/jsp/errorhdlr.jsp

页面。 errorhdlr.jsp页面包含清单17所示的代码:

清单17. errorhdlr.jsp的代码
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@page isErrorPage="true" %>
<HTML>
<HEAD>
</HEAD>
<BODY>
<H1>developerWorks Tomcat 5 Error Page</H1>

<br/>
<FONT  SIZE="4">
<c:if test="${(requestScope.BlueCounter != null) && 
    (requestScope.BlueCounter > 0)}">
  <font color="blue">The BlueFilter has been activated 
      ${requestScope.BlueCounter} times.</font><br/>
</c:if>
<c:if test="${(requestScope.RedCounter != null) && 
    (requestScope.RedCounter > 0)}">
  <font color="red">The RedFilter has been activated 
      ${requestScope.RedCounter} times.</font><br/>
</c:if>
<br/>
Error information:<br/>
<i>${pageContext.errorData.throwable.message}</i>
</FONT>
</BODY>
           

errorhdlr.jsp打印出过滤器计数器值,然后打印出使用EL捕获到异常的消息。

重新启动Tomcat 5并访问

http://<hostname>:8080/dvworks/jsp/errorgen.jsp

URL。 即使您尝试访问errorgen.jsp,也会显示errorhdlr.jsp(因为显式引发了异常)。 您将看到的错误页面如图11所示:

图11.请求和错误过滤
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

请注意,

RedFilter

已被调用两次-一次在REQUEST级别,第二次在容器转发到错误页面时。

您可以在处理自定义错误处理Web资源之前应用Servlet 2.4过滤器。

筛选器和Model 2 Web应用程序框架

使用流行的应用程序框架时,Tomcat 5中增强的过滤功能提供了新的部署机会。 诸如Struts和Turbine之类的框架(请参阅参考资料 )使用JSP Model 2体系结构将数据操作(MVC模式中的Model)与业务逻辑操作和表示操作(视图)分开。 它们的功能集中在使用交换Servlet(控制器Servlet)上,该Servlet根据静态XML指令文件或使用运行时自检将传入的请求转发到不同的用户创建的Action组件。 这些请求通常在组件之间进一步转发,直到响应完成。 使用Model 2架构,设计人员可以将其服务器端编码组件化,以适应变化和更轻松地进行长期维护。

因为调度程序被广泛用于在控制器Servlet与应用程序组件之间以及应用程序组件之间转发请求,所以您可以轻松地将过滤器添加到处理流程中。 通过Servlet 2.4的增强,过滤器现在可以成为Web应用程序组件混合中不可或缺的一部分-使用Model 2应用程序框架时。

硬件领域的架构模式

考虑到运行最大规模Web应用程序的服务器集群的新颖方法,是通过微处理器的类比。 传入请求跨越有限的可能操作集,例如CPU的指令集。 服务器对请求的处理类似于硬件内核对指令的执行。

将您的应用程序传递给高性能的未来

流水线是组件化请求处理的以请求流为中心的视图。 它着重于单个请求在应用服务器中流动时的多阶段处理。

在流水线模型中,请求流经装配线(流水线),同时在每个阶段由一系列处理器进行修改/转换/处理,直到最终生成响应并将其加速回客户端。 诸如Jakarta Struts和Turbine之类的流行Web应用程序框架已经凭借其JSP Model 2架构以流水线方式对服务器端逻辑进行了组件化。

图12显示了显示数据页面时Web应用程序或服务请求可能流经的典型管道阶段:

图12.管道模型的典型阶段
驯服烂代码_驯服Tomcat:Tomcat 5的过滤技巧

总而言之,请求通过表3中所示的三个管道阶段传递:

表3.管道模型阶段
管线阶段名称 典型操作说明
资料撷取 根据传入请求中的信息从外部系统(例如JDBC数据源)中获取数据
处理中 使用数据元素的值验证或转换数据元素以查找其他数据,例如来自内部或外部源的数据
渲染图 基于处理后的数据创建输出响应,通常以可视格式(例如HTML)或交换格式(例如XML)

通过将设计重点放在请求处理管道上,Web应用程序设计进入了性能调整和优化可能性的新时代。 在不久的将来,可以利用现代微处理器硬件设计中所利用的许多相同技术(用于优化管道)来优化Web应用程序服务器软件和硬件。

例如,在对处理流经数据提取阶段的每个请求有类似要求的Web应用程序中,经过优化的应用程序服务器/硬件平台可以预取并本地缓存最常访问的数据-完全消除了大多数应用程序的数据获取开销传入请求。 另一个示例-对于在渲染阶段需要大量CPU处理资源的请求(例如XSLT转换或其他XML解析),可以在管道的渲染阶段配置并行的一组硬件加速的XML处理器。 尽管就今天实现这些优化的实用能力而言,这些优化仍然有些过分未来,但考虑到流水线模型来设计Web应用程序是实现它们的必要元素。

结论

Tomcat 5的新过滤功能使我们能够利用Web应用程序的请求处理流程的每个阶段。 此功能有助于在应用程序框架中使用过滤器,并开辟了新的性能优化和调整可能性。

翻译自: https://www.ibm.com/developerworks/java/library/j-tomcat2/index.html