如果说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很难做到的
从别人空间里copy来的一张图,大体是差不多,但是该图没有体现出factort生产pipeline过程、try,catch,final部分和遍历valve的过程,待后续自己完善...
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风格”指的是什么?
说到设计理念,太大了,只是我的一些小小的感想吧~今天去参加了《框架设计原则》这门分享,因为最近正在学习框架,所以感觉这门课还是收获蛮多的。就这个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); } }