概述
如果说webx2.0框架是建立在service框架的基础之上,在学习完service框架之后,了解到serviceManager的管理模式,那么PipelineService便是控制着由serviceManager管理的service的调用顺序,控制这整个应用程序的走向;
service框架负责管理service的生命周期,而webx框架就是负责管理RunData的走向和生命周期,而其中最核心的就是PipelineService。
简单介绍下PipelineService,下面是本文主要解决的几个问题:
Pipeline在webx框架中的地位
service框架和webx框架的区别
Pipeline的工作原理
Pipeline的设计理念,结合框架设计原则谈了下自己的看法
最后抛出几个问题,先概要下,具体描述正文里有说明:
AbstractValve和Rundata这两个类在三个包中出现了,分别是service、webx和turbine,为什么这样设计,有什么优点?
TryCatchFinallyValve这个valve和webx框架紧紧耦合在一起,为什么不是由valve自己invoke呢?
会话域在整个生命周期里经历了这样的转型:从Rundata->PipelineContext->Rundata,仅仅是因为valve的invoke方法入参是PipelineContex,而我们在执行valve有的时候需要改变response等值,用到了Rundata,所以在turbine包里面又做了一次向下转型,然后去执行子类的Rundata入参的invoke方法,那为什么不废弃turbine包的AbstractValve类,直接继承自webx包的AbstractValve类呢?
引言
Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。在Webx中,pipeline的作用就是控制应用程序流程的走向。
Pipeline类似于filter,但是又区别于filter,主要有下面几个区别:
Pipeline只能控制流程,不能改变request和response;其实这已经由RundataService来控制了
Pipeline是轻量级组件,它甚至不依赖于WEB环境。Pipeline既可以在程序中直接装配,也可以由spring和schema来配置。
Pipeline支持更复杂的流程结构,例如:子流程、条件分支、循环等,这是filter很难做到的
PipeLineService工作原理
从别人空间里copy来的一张图,大体是差不多,但是该图没有体现出factort生产pipeline过程、try,catch,final部分和遍历valve的过程,待后续自己完善...
PipeLineService涉及到的类
服务类&实体类
1、PipelineService
2、valve
这里抛出个问题,从下面可以看到AbstractValve类位于3个包内:
com.alibaba.service.pipeline包内的AbstractValve只有一些初始化和配置的操作,没有具体的行为函数
com.alibaba.webx.pipeline包内的AbstractValve增加了WebxComponent的功能
com.alibaba.turbine.pipeline包内的AbstractValve增加了行为函数:invoke,同时预留了invoke(RunData rundata)给子类实现,自身的invoke(PipelineContext pipelineContext)只是做了PipelineContext对RunData的向下转型的判断
单单从功能上来看,虽然能看明白,但是对于作者为什么这么设计我没有总体上的认识!
同样的情形在Rundata也发生了,从代表servlet运行时的信息,封装了request和response等信息->提供和Webx相关的服务->提供和classic风格相关的服务,这里的“classic风格”指的是什么?
会话类
PipeLineService设计理念
说到设计理念,太大了,只是我的一些小小的感想吧~今天去参加了《框架设计原则》这门分享,因为最近正在学习框架,所以感觉这门课还是收获蛮多的。就这个service来说吧,我觉得它正符合了框架设计原则的“核心邻域模型”和“最少化概念模型”。
核心邻域模型
服务域
往往是入口,管理者实体域和会话域的生命周期,像Velocity的Engine、Spring的BeanFactory和PipelineService
实体域
表示你要操作的对象模型,不管什么产品,总有一个核心概念,大家都绕围它转。像Velocity的Template、Spring的Bean和PipelineService的一个个Pipeline和Valve
会话域
表示每次操作瞬时状态,操作前创建,操作后销毁。像Velocity的Context、Spring的Invocation和PipelineService的PipelineContext
设计的优势
结构清晰,可变与不可变状态分离。Pipeline被设计成单例对象,但是对于不同的访问,每个Pipeline又是有状态的,因此作者设计了PipelineContext分离于PipelineService和Pipeline,这样使得所有领域都线程安全,不需要加锁。
从上面的类图可以看到,Rundata继承了PipelineContext,在总结RundataService的时候没有感知到,这样做的好处是在于为了“最少化概念模型”,我们都知道Rundata是一个线程本地变量,如果兼具了PipelineContext的功能就能有效得最少化概念了。
设计分析
从代码的角度分析PipelineService的真相,并提出自己的疑问。
1、环境
<pipeline>
<valve class="com.alibaba.service.pipeline.TryCatchFinallyValve">
<try>
<valve class="com.alibaba.turbine.pipeline.SetLoggingContextValve"/>
<valve class="com.alibaba.turbine.pipeline.AnalyzeURLValve"/>
<valve class="com.alibaba.turbine.pipeline.CheckCsrfTokenValve" expiredPage="error.vm"/>
<valve class="com.alibaba.aliHome.common.CheckAdminValve" />
<valve class="com.alibaba.turbine.pipeline.ChooseValve" label="processModule">
<when extension="jsp, vm">
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
<valve class="com.alibaba.turbine.pipeline.PerformScreenTemplateValve"/>
</when>
<when extension="do">
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
<!--valve class="com.alibaba.turbine.pipeline.PerformScreenValve"/-->
</when>
<when target="/images/**">
<valve class="com.alibaba.aliHome.common.GetImageResourceValve"/>
</when>
<when target="/home/verifyCode/**">
<valve class="com.alibaba.aliHome.common.GetVerifyCodeValve"/>
</when>
</valve>
<valve class="com.alibaba.turbine.pipeline.RedirectTargetValve" goto="processModule"/>
</try>
<catch>
<!-- <valve target="error.vm" class="com.alibaba.turbine.pipeline.SetErrorPageValve"/>
<valve class="com.alibaba.turbine.pipeline.PerformScreenTemplateValve"/> -->
<valve class="com.alibaba.aliHome.common.ErrorLocationValve"/>
</catch>
<finally>
<valve class="com.alibaba.turbine.pipeline.SetLoggingContextValve" action="cleanup"/>
</finally>
</valve>
</pipeline>
从上面的配置可以看到该pipeline中只有一个valve:TryCatchFinallyValve,TryCatchFinallyValve中有三个子pipeline;照理说框架该是读取pipeline,然后执行valve,但是框架却和第一个valve发生了...,看下面分析
2、入口
这部分代码昭示着rundata的生命周期,同时也是pipeline的生命周期,在注释部分已经标明了何时走TryCatchFinallyValve的try、catch、final部分的pipeline,下面就直接分析try部分的pipeline。
在这里我提出一个疑问,为什么要框架来控制TryCatchFinallyValve的三个pipeline何时去调用,这种设计貌似使得框架和该TryCatchFinallyValve的耦合太强了,不赞成这种设计。(好不容易才找到的catch的pipeline在哪儿执行的,发现原来竟跟pipelineService都没啥关系,直接是框架调用的,汗!)
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- ......
- try {
- rundata = getRunData(request, response);
-
- if (!rundata.isRedirected()) {
- try {
- Profiler.enter("before request");
-
- beforeRequest(rundata);
- } finally {
- Profiler.release();
- beforeRequestCalled = true;
- }
-
- try {
- Profiler.enter("handle request");
- //pipeline的入口,处理的是try部分的pipeline
- handleRequest(rundata);
- } finally {
- Profiler.release();
- }
- }
- } catch (Throwable e) {
- requestException = e;
-
- // 处理异常e的过程:
- try {
- try {
- rundata.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- } catch (Exception ee) {
- // ignore this exception
- }
-
- Profiler.enter("handle exception");
-
- // 1. 处理的是catch部分的pipeline
- if (!handleException(rundata, request, response, e)) {
- try {
- // 2. 如果handleException放弃处理,则尝试生成简易的出错页面
- handleExceptionException(rundata, response, e, null);
- } catch (Throwable eee) {
- // 3. 放弃!把异常e交给servlet engine来处理
- handleHorribleException(e, eee);
- }
- }
- } catch (Throwable ee) {
- try {
- if ((ee == e)
- || ((ee instanceof ChainedThrowable)
- && (((ChainedThrowable) ee).getCause() == e))) {
- // 确保e和ee不是指同一件事
- ee = null;
- }
-
- if (ee != null) {
- log.error("Another exception occurred while handling exception "
- + e.getClass().getName(), ee);
- }
-
- // 2. 尝试生成简易的出错页面
- handleExceptionException(rundata, response, e, ee);
- } catch (Throwable eee) {
- // 3. 放弃!把异常e交给servlet engine来处理
- handleHorribleException(e, eee);
- }
- } finally {
- Profiler.release();
- }
- } finally {
- try {
- //提交rundata
- commitRunData(rundata);
- } catch (Exception e) {
- log.error("Exception occurred while commit rundata", e);
- } finally {
- if (beforeRequestCalled) {
- try {
- Profiler.enter("after request");
- //处理的是final部分的pipeline
- afterRequest(rundata);
- }
- protected void handleRequest(RunData rundata) throws WebxException {
- // 取得当前component的默认pipeline。
- PipelineService pipelineService = (PipelineService) getWebxController().getServiceManager()
- .getService(PipelineService.SERVICE_NAME,
- rundata.getComponent());
- Pipeline pipeline = pipelineService.getPipeline();
-
- if (pipeline == null) {
- throw new WebxException("Failed to get pipeline, the PipelineService may be failed to initialize");
- }
-
- TryCatchFinallyValve tryCatchFinally = null;
-
- // 取得try-catch-finally valve,特殊处理之。
- if ((pipeline.getLength() == 1) && pipeline.getValve(0) instanceof TryCatchFinallyValve) {
- tryCatchFinally = (TryCatchFinallyValve) pipeline.getValve(0);
- rundata.setAttribute(TRY_CATCH_FINALLY_VALVE_KEY, tryCatchFinally);
- }
-
- try {
- if (tryCatchFinally == null) {
- pipeline.invoke(rundata);
- } else {
- tryCatchFinally.invokeTryPipeline(rundata);
- }
- } catch (PipelineException e) {
- throw new WebxException(e);
- }
- }
3、执行Pipeline
先是执行valve的invoke方法
tryCatchFinally.invokeTryPipeline(rundata)
再找到该valve的tryPipeline,执行该Pipeline,值得注意的是:Rundata被转型了~
tryPipeline.invoke(pipelineContext);
执行Pipeline,每次执行pipeline的时候,该pipeline自身维护一份上下文:pipelineContext,其实就是一个当前valve的状态而已罢了!
- public void invoke(PipelineContext pipelineContext) throws PipelineException {
- // 清除step,重新开始。
- setStep(pipelineContext, 0);
-
- // 执行下一步。
- invokeNext(pipelineContext);
- }
根据pipleline的valves(初始化的时候生成)和线程本地的pipelineContext去执行每个valve,每个valve在invoke的时候会抛出异常,直到被DefaultWebxControllServlet捕捉到,转而执行tryCatchFinally的catch子Pipeline
- public void invokeNext(PipelineContext pipelineContext)
- throws PipelineException {
- ValveForward forward = null;
-
- for (int step = getStep(pipelineContext); step < getLength();
- step = getStep(pipelineContext)) {
- setStep(pipelineContext, step + 1);
-
- Valve valve = getValve(step);
-
- if (log.isDebugEnabled()) {
- String label = valve.getLabel();
-
- if (StringUtil.isEmpty(label)) {
- log.debug("Entering valve[step=" + step + "]: "
- + getValve(step).getClass().getName());
- } else {
- log.debug("Entering valve[step=" + step + ", label=" + label + "]: "
- + getValve(step).getClass().getName());
- }
- }
-
- try {
- //执行valve
- forward = valve.invoke(pipelineContext);
- } finally {
- if (log.isDebugEnabled()) {
- String label = valve.getLabel();
-
- if (StringUtil.isEmpty(label)) {
- log.debug("..exited valve[step=" + step + "]: "
- + getValve(step).getClass().getName());
- } else {
- log.debug("..exited valve[step=" + step + ", label=" + label + "]: "
- + getValve(step).getClass().getName());
- }
- }
- }
-
- if (forward != null) {
- break;
- }
- }
-
- if (forward != null) {
- log.debug("Forward to: " + forward);
-
- try {
- forward.invoke(this, pipelineContext);
- } finally {
- log.debug("Forward returned: " + forward);
- }
- }
- }
做转型,因为向下转型有风险,因此由此一步,既然valve执行过程中可能需要修改request和reponse,所以用到了rundata,但为什么不把出现pipelineContext的地方都改成rundata呢?这是又一个疑惑!
- public abstract class AbstractValve extends com.alibaba.webx.pipeline.AbstractValve {
-
- public ValveForward invoke(PipelineContext pipelineContext)
- throws PipelineException {
- RunData turbineRundata = null;
-
- try {
- turbineRundata = (RunData) pipelineContext;
- } catch (ClassCastException e) {
- throw new PipelineException(RunData.class.getName() + " expected", e);
- }
-
- return invoke(turbineRundata);
- }
执行valve的invoke函数
- public ValveForward invoke(RunData rundata) throws PipelineException {
- ......
- Return null;
- }
下面看看遇到有goto和continue的valve是怎么搞的,举个goto的例子吧:
- public void invokeNext(PipelineContext pipelineContext)
- throws PipelineException {
- ValveForward forward = null;
-
- for (int step = getStep(pipelineContext); step < getLength();
- step = getStep(pipelineContext)) {
- setStep(pipelineContext, step + 1);
-
- ......
-
- if (forward != null) {
- break;
- }
- }
-
- if (forward != null) {
- log.debug("Forward to: " + forward);
-
- try {
- forward.invoke(this, pipelineContext);
- } finally {
- log.debug("Forward returned: " + forward);
- }
- }
- }
开始跑ValveForward的invoke方法,这里把pipeline和pipelineContext保存起来是很有必要的
- public ValveForward invoke(RunData rundata) throws PipelineException {
- if (label == null) {
- throw new PipelineException("Missing attribute \"goto\"");
- }
-
- // 如果redirectTarget被设置,则继续执行,否则退出。
- ValveForward gotoLabel = null;
- String target = rundata.getTarget();
- String redirectTarget = rundata.getRedirectTarget();
-
- if (StringUtil.isNotEmpty(redirectTarget) && !StringUtil.equals(target, redirectTarget)) {
- rundata.setTarget(redirectTarget);
- rundata.setRedirectTarget(null);
-
- gotoLabel = new GotoLabel(label);
- }
-
- return gotoLabel;
- }
开始修改context执行跳转
- public class GotoLabel implements ValveForward {
- private String label;
-
- /**
- * 创建<code>GotoLabel</code>对象。
- *
- * @param label label名,不能为空
- */
- public GotoLabel(String label) {
- this.label = StringUtil.trimToNull(label);
- }
-
- public void invoke(Pipeline pipeline, PipelineContext pipelineContext)
- throws PipelineException {
- if (this.label == null) {
- throw new PipelineException("Label should not be empty");
- }
- //这里很明显是修改step,然后继续跑一个个的valve,仔细分析分析,还是有点意思的!尤其是这个invokeNext名字起的,
- //要是在初次调用pipeline的invoke方法的时候见到这个invokeNext还真的不知道作者咋起这么个名字!
- pipeline.gotoLabel(pipelineContext, label);
- pipeline.invokeNext(pipelineContext);
- }
- }