Webx学习笔记(六)Pipeline服务

1. Pipeline工作原理

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

Webx学习笔记(六)Pipeline服务_第1张图片

                                             图 6.4. Pipeline和Valves

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

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

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

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

 

2. Pipeline的用途

 

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

 

      一个典型的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>

3. Pipeline的使用

       一个简单的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。

     在代码中执行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对象。

调用子流程

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

Webx学习笔记(六)Pipeline服务_第2张图片

                         图 6.5. Pipeline和子流程

     子流程是从valve中发起的。下面的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>

中断一个pipeline

       Pipeline可以被中断。当有多级子pipeline时,你可以中断到任何一级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

Webx学习笔记(六)Pipeline服务_第3张图片

                                         图 6.6. 中断一个pipeline

      下面的valve将子流程执行了至多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();
    }
}

存取pipeline的状态

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

       在valve中存取pipeline的状态

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

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

现成可用的valves

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

     无条件循环 - <loop>
<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>

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

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

  定义循环变量loopCounterName,这个变量值将被保存在PipelineContext中,且可被其它的valve所访问。通过判断循环变量“count <= 2”,循环2次。定义maxLoopCount=10,以避免循环失控。   

   单条件分支if

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

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

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

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

  无条件中断 - <break>

<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>

<services:pipeline>
    <loop loopCounterName="count">
        <valve />
        <break-if test="count > 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 <= 2" />  
        <valve />
    </loop>
</services:pipeline>

   count>2时中断。<break-if><break-unless>均支持和<break>类似的其它选项:levelstoLabel。和<if>类似,也支持任意condition。<break-unless><break-if>的条件相反:除非count<=2,否则中断。

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

<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>

<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>

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

条件

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

   Condition接口

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

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

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


//以上配置可以简化为:

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

    Webx还提供了三个组合式的条件。

//相当于java中&&
<all-of>
    <condition1 />
    <condition2 />
    <condition3 />
</all-of>


//相当于java||
<any-of>
    <condition1 />
    <condition2 />
    <condition3 />
</any-of>


//相当于java!
<none-of>
    <condition1 />
    <condition2 />
    <condition3 />
</none-of>

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

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

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

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

你可能感兴趣的:(pipeline)