1. Pipeline工作原理
Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。在Webx中,pipeline的作用就是控制应用程序流程的走向。
图 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对象而已。
图 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
图 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>
类似的其它选项:levels
和toLabel
。和<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,又想要控制流程;
- 如果你希望独立于任何框架。