第 6 章 Filter、Request Contexts和Pipeline

6.1. Filter
6.1.1. Filter的用途
6.1.2. Filter工作原理
6.1.3. Filter的限制
6.1.4. Webx对filter功能的补充
6.2. Request Contexts服务
6.2.1. Request Contexts工作原理
6.2.2. Request Contexts的用途
6.2.3. Request Contexts的使用
6.3. Pipeline服务
6.3.1. Pipeline工作原理
6.3.2. Pipeline的用途
6.3.3. Pipeline的使用
6.4. 本章总结

Filter是Servlet规范2.3版及更新版所支持的一种机制。和Servlet/JSP不同,Filter自己往往不会直接产生response,相反,它提供了一种“符加”的功能,可以作用在任何一个servlet、JSP以及其它filter之上。然而,在实际的应用中,我们发现filter有很多不足之处。

Webx框架提供了两种机制(Request Contexts和Pipeline)来作为filter机制的补充。在大多数情况下,它们都可以实现类似filter的功能,但比filter更容易扩展、更容易配置、也更轻量。Webx并没有打算完全替代filter,相反它还是可以和任何filter搭配使用。

本章先简略介绍filter的功能和不足,再向你介绍Request Contexts和Pipeline的工作原理,及使用方法。

6.1. Filter

6.1.1. Filter的用途

Filter这种机制常被用来实现下面的功能:

页面授权根据登录用户的权限,阻止或许可用户访问特定的页面。
日志和审计记录和检查用户访问WEB应用的情况。
图片转换改变图片的格式、精度、尺寸等。
页面压缩压缩页面内容,加快下载速度。
本地化显示本地语言和风格的页面。
XSLT转换对XML内容进行XSLT转换,使之适用于多种客户端。
高速缓存高速缓存页面,提高响应速度。

当然还有更多种的应用,我们不可能一一列举。

Filter的通用性很好。任何filter均独立于其它filter和servlet,因此它可以和任意其它filter和servlet组合搭配。下面是一段配置示例 ── 通过SetLoggingContextFilter,日志系统可以记录当前请求的信息,例如:URL、referrer URL、query string等。

例 6.1. Filter配置示例(/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    <filter>
        <filter-name>mdc</filter-name>
        <filter-class>com.alibaba.citrus.webx.servlet.SetLoggingContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>mdc</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

6.1.2. Filter工作原理

Filter Chain

图 6.1. Filter Chain

如图所示。多个filter和至多一个servlet被串联成一个链,被称为Filter Chain。执行的时候,引擎将控制权交给链条中的头一个filter(如果有的话)。然后,就像击鼓传花一样,控制权被依次传递给filter chain中的下一个filter或servlet。每一个得到控制权的filter可以做下面的事:

  • 继续传递控制权或立即终止filter chain。

    • Filter可将控制权传递给链条中的下一个filter或者最终的servlet。

    • Filter也可以不将控制权传递给下一个filter或servlet,这样便中止了整个filter chain的执行。

  • 预处理。在传递控制权给下一个filter或servlet之前,filter可以预先做一些事情:

    • 设置request、response中的参数,例如:character encoding、content type等。

    • HttpServletRequestWrapper传递给链条中的下一位,filter可以通过wrapper改变request中的任意值。

    • HttpServletResponseWrapper传递给链条中的下一位,filter可以通过wrapper来拦截后续filter或servlet对response的修改。

  • 提交。在控制权从filter chain中返回以后,filter还可以做一些后续提交的操作。

    • 例如,将response中拦截而来的数据,压缩或转换格式,并发送给客户端或filter chain的上一级。

    • 通过trycatch还可以捕获filter chain下一级所有的异常,并做处理。

6.1.3. Filter的限制

Filter是很有用的。作为servlet的补充,filter也是很成功的。但是filter并没有被设计用来完成一切事情。事实上,filter的设计限制了filter的用途。每个filter具有下面的限制:

  • Filter可以访问和修改数据。但它只能访问和修改HttpServletRequestHttpServletResponseServletContext等容器级的对象,而不能(或很难)访问应用程序中的状态。所以filter无法实现和应用逻辑密切相关的功能。

  • Filter可以影响执行流程。但它不能改变filter chain的结构和顺序。Filter chain的结构和顺序是由web.xml中定义的。当filter得到控制权以后,它只能选择继续下去,或者立即结束,而没法进行循环、分支、条件判断等更复杂的控制。因此,filter只能用来实现粗粒度的流程控制功能(例如,当用户未获授权时,停止执行filter chain),难以应付更细致的应用程序内的控制需求。

  • Filter与其它filter和servlet之间,除了request和response对象以外,无法共享其它的状态。这既是优点又是缺点。优点是使filter更独立、更通用;缺点是filter与其它filter、servlet之间难以协作,有时甚至会引起无谓的性能损失。

6.1.4. Webx对filter功能的补充

综上所述,一个filter常常做的两件事是:

  • 改变request/response对象(通过HttpServletRequestWrapperHttpServletResponseWrapper);

  • 改变应用执行的流程。

其实,大部分filter只做其中一件事。例如:

  • 页面压缩filter仅仅改变response,并不改变应用的流程。

  • 页面授权filter根据当前请求用户的身份,判定他是否有权限访问当前页面。这个filter会影响应用流程,却不会去改变request和response。

当然也有例外。有一些filter不做上面两件事中任何一件。例如,日志filter仅仅读取request对象并记录日志而已,既不改变request/response,也不影响应用的流程。还有一些filter同时做上面两件事。比如高速缓存页面的filter不仅要修改response,而且当cache被命中时,不再执行下一步的流程,而是直接返回cache中的内容,以提高性能。

Webx框架提供了两个服务,正好吻合了上述两个最常用的filter的功能。

Request Contexts服务该服务负责访问和修改request和response,但不负责改变应用执行的流程。
Pipeline服务提供应用执行的流程,但不关心request和response。

虽然这两个服务看起来和filter的功能类似,但是它们远比filter要强大和方便 ── 它们克服了上述filter的几个限制:

  • 和Filter不同,Request Contexts和Pipeline服务可以访问应用内部的状态和资源,效率更高,功能更强。

  • 和Filter不同,Pipeline服务可以定义灵活(但仍然简单)地控制应用的流程 。Pipeline不仅可以控制流程的中断或继续,还可以实现子流程、循环、条件转移、异常处理等更精细的流程控制。Pipeline服务甚至可以运用在非WEB的环境中。

  • 和Filter不同,Request Contexts服务中的每一个环节(Request Context)之间并非完全独立、互不干涉的。每个request context可以访问它所依赖的其它request context中的状态。

6.2. Request Contexts服务

6.2.1. Request Contexts工作原理

Request Context,顾名思义,就是一个请求的上下文。事实上,你可以把Request Context看作是HttpServletRequestHttpServletResponse这两个对象的总和。除此之外,多个Request Context可以被串接起来,被称为Request Context Chain,类似于filter chain。

Request Context Chain

图 6.2. Request Context Chain

如上图所示,每一个Request Context都可以包括两个基本的操作:“预处理”和“提交”。

  • 在一个请求开始的时候,每个Request Context的“预处理”过程被依次调用。最内层的(即最先的)Request Context最先被调用,最外层的(即最后的)Request Context最后被调用;

  • 在一个请求结束的时候,每个Request Context的“提交”过程被依次调用。和“预处理”的顺序相反,最外层的(即最后的)Request Context最先被调用,最内层的(即最先的)Request Context最后被调用。

Request Context在预处理的时候,可以利用HttpServletRequestWrapperHttpServletResponseWrapper来包装和修改request和response ── 这一点和filter相同。每一层Request Context,都会增加一个新的特性。最先的Request Context成为最内层的包装,最后的Request Context成为最外层的包装。如下图所示。

Request Contexts的嵌套

图 6.3. Request Contexts的嵌套

和filter原理中的图进行对比,你会发现,尽管Request Contexts和Filter的执行方案有明显的不同,但是Request Contexts预处理和提交的顺序是和filter chain完全一致的。预处理时,由内层执行到外层;提交时,反过来由外层执行到内层。不同的是,filter能够决定是否继续传递控制权给filter chain中的下一位,而Request Context则没有这个权利。

6.2.2. Request Contexts的用途

Webx目前提供了以下几种request context的实现,每个都有独特的功能。

表 6.1. Request Contexts的功能

名称功能
<basic>提供基础安全特性,例如:过滤response headers、cookies,限制cookie的大小等。
<buffered>缓存response中的内容。
<lazy-commit>延迟提交response。
<parser>解析参数,支持multipart/form-data(即上传文件请求)。
<rewrite>重写请求的URL和参数。
<session>一套可扩展的session框架,重新实现了HttpSession接口。
<set-locale>设置locale区域和charset字符集编码。
[注意]注意

本章对以上所有的request contexts的功能和用法不作具体的介绍,详情请参阅第 7 章 Request Contexts功能指南第 8 章 Request Context之Session指南

需要特别指出的是,你还可以扩展出更多的Request Context,以实现新的功能。

6.2.3. Request Contexts的使用

6.2.3.1. 配置

除了下面例子所示的一段配置之外,你不需要做太多的事,就可以使用Request Contexts。因为Request Contexts对于应用来说是透明的 ── 多数应用只需要依赖于HttpServletRequestHttpServletResponse就可以了。

例 6.2. Request Context的配置(/WEB-INF/webx.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:request-contexts="http://www.alibaba.com/schema/services/request-contexts"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.alibaba.com/schema/services
                http://localhost:8080/schema/services.xsd
        http://www.alibaba.com/schema/services/request-contexts
                http://localhost:8080/schema/services-request-contexts.xsd
        http://www.springframework.org/schema/beans
                http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">
    ...

    <services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
        <basic />
        <buffered />
        <lazy-commit />
        <parser />
        <set-locale defaultLocale="zh_CN" defaultCharset="UTF-8" />
        <!-- Optional -
        <session />
        <rewrite />
        -->
    </services:request-contexts>

    <services:upload sizeMax="5M" />

</beans:beans>

由于使用了SpringExt的schema机制,所以在支持schema的XML编辑器的帮助下,很容易书写和验证Request Contexts的配置。

6.2.3.2. 排序

Request Contexts之间,有时会有依赖关系,所以Request Contexts出现的先后顺序是非常重要的。例如,

  • <session>提供了基于cookie的session支持。然而cookie属于response header。一旦response被提交,header就无法再修改了。因此<session>依赖于<lazy-commit>,以阻止response过早提交。也就是说,<lazy-commit>必须排在<session>之前。

  • <rewrite>需要访问参数,而参数是能过<parser>解析的,所以<parser>要排在<rewrite>之前。

类似的约束还有很多。如果把Request Contexts的顺序排错,可能会导致某项功能错误或失效。然而,对于一般的应用开发者而言,这些约束往往是神秘的、并非显而易见的,需要经过细致地分析才能了解它们。

好在Request Contexts内部提供了一个机制,可以根据预定义的约束条件,对所有的Request Contexts进行自动排序。和Filter不同,应用开发者不需要在意Request Contexts在配置文件中的排列顺序,就可以保证所有的Request Contexts能够正常工作。下面的两种配置文件是等效的:

例 6.3. Request Contexts等效配置1

<services:request-contexts>
    <basic />
    <buffered />
    <lazy-commit />
    <parser />
    <set-locale />
    <session />
    <rewrite />
</services:request-contexts>

例 6.4. Request Contexts等效配置2

<services:request-contexts>
    <rewrite />
    <session />
    <set-locale />
    <parser />
    <lazy-commit />
    <buffered />
    <basic />
</services:request-contexts>

6.2.3.3. 访问特定的Request Context

一般来说,Request Contexts对于应用程序是透明的 ── 也就是说,应用程序最多只需要访问Servlet API中的接口:HttpServletRequestHttpServletResponse即可,就好像Request Contexts不存在一样。

比如,Request Context <parser>能够解析multipart/form-data类型的请求(即上传图片请求)。但你不需要用另一个API来访问请求中的普通数据,你只需要用HttpServletRequest中定义的方法就可以访问,仿佛这是一个普通的请求:

例 6.5. 访问任意类型的请求中的参数

String value = request.getParameter("myparam");

再比如,Request Context <session>重新实现了HttpSession的接口,但是应用程序并不需要关心这些,他们还是和原来一样访问session:

例 6.6. 访问session

HttpSession session = request.getSession();

String value = (String) session.getAttribute("myattr");
session.setAttribute("myattr", newValue);

然而,有一些功能在原有的Servlet API中是不存在的。对于这一类功能,你必须访问特定的RequestContext接口,才能使用它们。例如,你只能用另一个API才能读取用户上传的文件。下面的代码可以用来取得上传文件的信息:

例 6.7. 访问特定的RequestContext接口

ParserRequestContext parserRequestContext =
                RequestContextUtil.findRequestContext(request, ParserRequestContext.class);

ParameterParser params = parserRequestContext.getParameters();

FileItem myfile = params.getFileItem("myfile");

String filename = myfile.getName();
InputStream istream = myfile.getInputStream();

另外有一些功能,使用Request Context接口比原来的Servlet API接口更方便。例如,原来的request.getParameter()方法只能取得字符串的参数值,但是利用ParserRequestContext所提供的接口,就可以直接取得其它类型的值:

例 6.8. 通过ParserRequestContext接口访问参数比HttpServletRequest更方便

ParameterParser params = parserRequestContext.getParameters();

String stringValue = params.getString("myparam"); // 取得字符串值,默认为null
int intValue = params.getInt("myparam"); // 取得整数值,默认为0
boolean booleanValue = params.getBoolean("myparam", true); // 取得boolean值,指定默认值为true

6.2.3.4. 注入request作用域的对象

Spring最强大的功能是依赖注入。但是依赖注入有一个限制:小作用域的对象不能被注入到大作用域的对象。你不能够把request和session作用域的对象注入到singleton对象中。前者在每次WEB请求时,均会创建新的实例,每个线程独享这个request/session作用域的对象;后者是在Spring初始化或第一次使用时被创建,然后被所有的线程共享。假如你把某个request/session作用域的对象意外注入到singleton对象中,将可能产生致命的应用错误,甚至导致数据库的错乱。

表 6.2. Webx中的重要对象及其作用域

对象类型作用域
ServletContextSingleton scope
HttpServletRequestRequest scope
HttpServletResponseRequest scope
HttpSessionSession scope
所有RequestContext对象,如:ParserRequestContextSessionRequestContextRequest scope

在一般的情况下,对于一个singleton对象而言,例如,Webx中的action module、pipeline valve对象等,下面的代码是错误的:

例 6.9. 在action(singleton对象)中注入request scope的对象

public class MyAction {
    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private ParserRequestContext parser;
}

因为你不能把一个短期的对象如request、response和request context注入到MyAction这个singleton对象。然而,在Webx中,这样做是可以的!奥秘在于Request Contexts服务对上表所列的这些短期对象作了特殊的处理,使它们可以被注入到singleton对象中。事实上,被注入的只是一个“空壳”,真正的对象是在被访问到的时候才会从线程中取得的。

Webx鼓励应用程序使用singleton作用域的对象,不仅更简单,也更高效。经过上述技术处理以后,singleton对象访问request作用域对象的方法被大大简化了。

6.3. Pipeline服务

6.3.1. Pipeline工作原理

Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。在Webx中,pipeline的作用就是控制应用程序流程的走向。

Pipeline和Valves

图 6.4. Pipeline和Valves

Pipeline的设计和filter非常相似,也是击鼓传花式的流程控制。但是有几点不同:

  • Pipeline只能控制流程,不能改变request和response。

  • Pipeline是轻量级组件,它甚至不依赖于WEB环境。Pipeline既可以在程序中直接装配,也可以由spring和schema来配置。

  • Pipeline支持更复杂的流程结构,例如:子流程、条件分支、循环等。

6.3.2. Pipeline的用途

Pipeline可以说是Webx框架的核心功能之一。利用pipeline,你可以定制一个请求处理过程的每一步。

例 6.10. 一个典型的Webx应用的pipeline配置文件(pipeline.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:pl-conditions="http://www.alibaba.com/schema/services/pipeline/conditions"
    xmlns:pl-valves="http://www.alibaba.com/schema/services/pipeline/valves"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.alibaba.com/schema/services
                http://localhost:8080/schema/services.xsd
        http://www.alibaba.com/schema/services/pipeline/conditions
                http://localhost:8080/schema/services-pipeline-conditions.xsd
        http://www.alibaba.com/schema/services/pipeline/valves
                http://localhost:8080/schema/services-pipeline-valves.xsd
        http://www.springframework.org/schema/beans
                http://localhost:8080/schema/www.springframework.org/schema/beans/spring-beans.xsd
    ">

    <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">

        <!-- 初始化turbine rundata,并在pipelineContext中设置可能会用到的对象(如rundata、utils),以便valve取得。 -->
        <prepareForTurbine />

        <!-- 设置日志系统的上下文,支持把当前请求的详情打印在日志中。 -->
        <setLoggingContext />

        <!-- 分析URL,取得target。 -->
        <analyzeURL homepage="homepage" />

        <!-- 检查csrf token,防止csrf攻击和重复提交。 -->
        <checkCsrfToken />

        <loop>
            <choose>
                <when>
                    <!-- 执行带模板的screen,默认有layout。 -->
                    <pl-conditions:target-extension-condition extension="null, vm, jsp" />
                    <performAction />
                    <performTemplateScreen />
                    <renderTemplate />
                </when>
                <when>
                    <!-- 执行不带模板的screen,默认无layout。 -->
                    <pl-conditions:target-extension-condition extension="do" />
                    <performAction />
                    <performScreen />
                </when>
                <otherwise>
                    <!-- 将控制交还给servlet engine。 -->
                    <exit />
                </otherwise>
            </choose>

            <!-- 假如rundata.setRedirectTarget()被设置,则循环,否则退出循环。 -->
            <breakUnlessTargetRedirected />
        </loop>

    </services:pipeline>

</beans:beans>

6.3.3. Pipeline的使用

6.3.3.1. 创建一个valve

例 6.11. 一个简单的valve实现

public class MyValve implements Valve {
    public void invoke(PipelineContext pipelineContext) throws Exception {
        System.out.println("valve started.");

        pipelineContext.invokeNext(); // 调用后序valves

        System.out.println("valve ended.");
    }
}

配置(pipeline.xml

<services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    ...
    <valve class="com.alibaba.myapp.pipeline.MyValve" />
    ...
</services:pipeline>

上面的代码和配置创建了一个基本的valve ── 事实上,它只是打印了一些消息,然后把控制权传递给后序的valves。

6.3.3.2. 执行一个pipeline

例 6.12. 在代码中执行pipeline

@Autowired
private Pipeline myPipeline;

public void invokePipeline() {
    PipelineInvocationHandle invocation = myPipeline.newInvocation();

    invocation.invoke();

    System.out.println(invocation.isFinished());
    System.out.println(invocation.isBroken());
}

从spring容器中取得一个pipeline对象以后(一般是通过注入取得),我们就可以执行它。上面代码中,PipelineInvocationHandle对象代表此次执行pipeline的状态。Pipeline执行结束以后,访问invocation对象就可以了解到pipeline的执行情况 ── 正常结束还是被中断?

Pipeline对象是线程安全的,可被所有线程所共享。但PipelineInvocationHandle对象不是线程安全的,每次执行pipeline时,均需要取得新的invocation对象。

6.3.3.3. 调用子流程

Pipeline支持子流程。事实上,子流程不过是另一个pipeline对象而已。

Pipeline和子流程

图 6.5. Pipeline和子流程

子流程是从valve中发起的。下面的Valve代码启动了一个子流程。

例 6.13. 在valve中发起一个子流程

public class MyNestableValve implements Valve {
    private Pipeline subPipeline;

    public void setSubPipeline(Pipeline subPipeline) {
        this.subPipeline = subPipeline;
    }

    public void invoke(PipelineContext pipelineContext) throws Exception {
        // 发起子流程,以当前流程的pipelineContext为参数
        PipelineInvocationHandle subInvocation = subPipeline.newInvocation(pipelineContext);

        subInvocation.invoke();

        System.out.println(subInvocation.isFinished());
        System.out.println(subInvocation.isBroken());

        pipelineContext.invokeNext(); // 别忘了调用后序的valves
    }
}

配置文件(pipeline.xml

<services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    ...
    <valve class="com.alibaba.myapp.pipeline.MyNestableValve" p:subPipeline-ref="subPipeline" />
    ...
</services:pipeline>

6.3.3.4. 中断一个pipeline

Pipeline可以被中断。当有多级子pipeline时,你可以中断到任何一级pipeline。

例 6.14. 中断一个pipeline

pipelineContext.breakPipeline(0); // level=0,中断当前pipeline
pipelineContext.breakPipeline(1); // level=1,中断上一级pipeline

pipelineContext.breakPipeline("label"); // 中断到指定label的上级pipeline
// 以上调用相当于:
pipelineContext.breakPipeline(pipelineContext.findLabel("label"));

pipelineContext.breakPipeline(Pipeline.TOP_LABEL); // 终止所有pipelines
中断一个pipeline

图 6.6. 中断一个pipeline

6.3.3.5. 条件分支、循环

条件分支和循环其实只不过是子流程的运用而已:

条件分支根据一定的条件,来决定是否要执行子流程、执行哪一个子流程(多条件分支)。
循环多次执行子流程。

下面的valve将子流程执行了至多10遍。如果子流程内部中断了流程,则循环终止。

例 6.15. 将子流程循环执行10次

public class Loop10 implements Valve {
    private Pipeline loopBody;

    public void setLoopBody(Pipeline loopBody) {
        this.loopBody = loopBody;
    }

    public void invoke(PipelineContext pipelineContext) throws Exception {
        PipelineInvocationHandle handle = loopBody.newInvocation(pipelineContext);

        for (int i = 0; i < 10 && !handle.isBroken(); i++) {
            handle.invoke();
        }

        pipelineContext.invokeNext();
    }
}

6.3.3.6. 存取pipeline的状态

当一个pipeline在运行时,你可以通过PipelineContext取得一些上下文信息:

例 6.16. 在valve中存取pipeline的状态

pipelineContext.index(); // 当前valve在pipeline中的序号
pipelineContext.level(); // 当前pipeline在所有子pipeline中的级别
pipelineContext.isBroken(); // 当前pipeline是否已经被中断
pipelineContext.isFinished(); // 当前pipeline的所有valves是否已经执行完

// 存取任意数据
pipelineContext.getAttribute(key);
pipelineContext.setAttribute(key, value);

6.3.3.7. 现成可用的valves

一般情况下,你并不需要写前面例子中的代码,因为Webx已经为你提供了一系列现成的valves来实现同样的功能。

无条件循环 - <loop>

例 6.17. 无条件循环

<services:pipeline>
    <loop loopCounterName="count" maxLoopCount="10"> 
        <valve />
        <break-if test="..." /> 
    </loop>
</services:pipeline>

定义循环变量loopCounterName,这个变量值将被保存在PipelineContext中,且可被其它的valve所访问。

定义maxLoopCount=10最大循环圈数,以避免循环失控。

无条件循环一定要和<break><break-if><break-unless>等valve相配合。

条件循环 - <while>

例 6.18. 条件循环

<services:pipeline>
    <while loopCounterName="count" test="count &lt;= 2"> 
        <valve />
    </while>

    <while maxLoopCount="10"> 
        <conditions:condition class="..." /> 
        <valve />
    </while>
</services:pipeline>

定义循环变量loopCounterName,这个变量值将被保存在PipelineContext中,且可被其它的valve所访问。

通过判断循环变量“count <= 2”,循环2次。

定义maxLoopCount=10,以避免循环失控。

可以自定义任意条件。

单条件分支 - <if>

例 6.19. 单条件分支

<services:pipeline>
    <if test="1 == 2"> 
        <valve />
    </if>

    <if>
        <conditions:condition class="..." /> 
        <valve />
    </if>
</services:pipeline>

JEXL条件表达式。

自定义任意条件。

多条件分支 - <choose><when><otherwise>

例 6.20. 多条件分支

<services:pipeline>
    <choose>
        <when test="1 == 2"> 
            <valve />
        </when>
        <when> 
            <conditions:condition class="..." />
            <valve />
        </when>
        <otherwise> 
            <valve />
        </otherwise>
    </choose>
</services:pipeline>

条件分支1,用JEXL表达式来判断。

条件分支2,用任意条件判断。

分支3,当所有条件均不符合时,选择该分支。

无条件中断 - <break>

例 6.21. 无条件中断

<services:pipeline>
    <loop> 
        <valve />
        <break /> 
        <valve />
    </loop>

    <loop> 
        <valve />
        <loop>
            <break levels="1" /> 
        </loop>
        <valve />
    </loop>

    <loop label="MY_LOOP"> 
        <valve />
        <loop>
            <break toLabel="MY_LOOP" /> 
        </loop>
        <valve />
    </loop>
</services:pipeline>

无条件中止当前的pipeline(即loop循环)。

无条件中止上一层(levels=1)的pipeline(即loop循环)。

无条件中止指定label的pipeline(即loop循环)。

有条件中断 - <break-if><break-unless>

有条件中断是<break><if>的组合。

例 6.22. 有条件中断

<services:pipeline>
    <loop loopCounterName="count">
        <valve />
        <break-if test="count &gt; 2" /> 
        <valve />
    </loop>

    <loop label="MY_LOOP">
        <valve />
        <break-if toLabel="MY_LOOP"> 
            <conditions:condition class="..." />  
        </break-if>
        <valve />
    </loop>

    <loop loopCounterName="count">
        <valve />
        <break-unless test="count &lt;= 2" />  
        <valve />
    </loop>
</services:pipeline>

count>2时中断。

<break-if><break-unless>均支持和<break>类似的其它选项:levelstoLabel

<if>类似,也支持任意condition。

<break-unless><break-if>的条件相反:除非count<=2,否则中断。

无条件退出整个pipeline - <exit>

退出整个pipeline,意思是结束所有的嵌套层次。

例 6.23. 无条件退出整个pipeline

<services:pipeline>
    <loop>
        <valve />
        <loop>
            <exit />
        </loop>
        <valve />
    </loop>
</services:pipeline>

对于Webx而言,<exit>还有一层特殊的含义:放弃WebxFrameworkFilter的控制权,把它交还给servlet engine。以URL http://localhost:8081/myapp/myimage.jpg为例,把控制权交还给servlet engine,意味着让servlet engine去显示myapp应用目录下的静态图片:myimage.jpg

异常捕获和finally处理 - <try-catch-finally>

类似Java中的try/catch/finally结构。

例 6.24. 异常捕获和finally处理

<services:pipeline>
    <try-catch-finally>
        <try>
            <valve />
        </try>
        <catch exceptionName="myexception"> 
            <valve />
        </catch>
        <finally>
            <valve />
        </finally>
    </try-catch-finally>
</services:pipeline>

<catch>标签可以将捕获的异常以指定名称保存在PipelineContext中,以便其它valve取得。

创建子流程 - <sub-pipeline>

单纯使用这个valve,对执行结果不会有任何影响。但可用来对较长的pipeline进行分段管理。

例 6.25. 创建子流程

<services:pipeline>
    <valve />
    <sub-pipeline label="mylabel">
        <valve />
    </sub-pipeline>
    <valve />
</services:pipeline>

6.3.3.8. 条件

在前文所述的各种条件valve(例如<if><when><while><break-if><break-unless>等)中,都用到一个共同的对象:condition。Condition是一个简单的接口。

例 6.26. Condition接口

public interface Condition {
    /**
     * 如满足条件,则返回<code>true</code>。
     */
    boolean isSatisfied(PipelineStates pipelineStates);
}

为了方便起见,Webx默认提供了一个JexlCondtion

例 6.27. 使用JexlCondition

<if>
    <conditions:jexl-condition expr="loopCount == 2" />
    <break />
</if>

以上配置可以简化为:

<if test="loopCount == 2">
    <break />
</if>

JEXL表达式是Apache的一个小项目,表达式语法详见:http://commons.apache.org/jexl/reference/syntax.html。在JEXL表达式中,你可以使用pipelineContext.getAttribute()所能取得的所有状态值。例如,loop循环时,如果你设置了loopCounterName,那么循环计数器就可以被JEXL表达式所访问。

除此之外,Webx还提供了三个组合式的条件。

<all-of>

要求所有条件均满足,相当于Java中的&&操作符。

例 6.28. 组合式的条件:<all-of>

<all-of>
    <condition1 />
    <condition2 />
    <condition3 />
</all-of>
<any-of>

只要求任一条件满足,相当于Java中的||操作符。

例 6.29. 组合式的条件:<any-of>

<any-of>
    <condition1 />
    <condition2 />
    <condition3 />
</any-of>
<none-of>

要求所有条件均不满足,相当于Java中的!操作符。

例 6.30. 组合式的条件:<none-of>

<none-of>
    <condition1 />
    <condition2 />
    <condition3 />
</none-of>

这三个组合式条件可以互相组合,以构成任意复杂的条件判断语句。

6.4. 本章总结

Request Contexts和Pipeline是Webx框架中的两个核心服务。它们分别从两个方面实现了原本需要由Filter来实现的功能 ── Request Contexts提供了包装和修改request/response的机制,而pipeline则提供了流程控制的能力。Request contexts和pipeline组合起来的功能比servlet filter机制更加强大。因为它们是基于Spring的轻量组件,其性能、配置的方便性、扩展性都优于filter。

当然,Request Contexts和Pipeline并不想取代filter。在好几种场合,filter仍然是唯一的选择:

  • 如果你既想要修改request/response,又想要控制流程;

  • 如果你希望独立于任何框架。

但在你接到一个需求,正打算用filter来实现之前,请考虑一下,是否可以采用Webx所提供的这两种机制来取代。倘若可行,必然会带来更多的好处。