当前位置:   article > 正文

webx2.0-PipelineService学习笔记

webx pipeline return

概述

如果说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的过程,待后续自己完善...

145520_VIcJ_947581.png

PipeLineService涉及到的类

服务类&实体类

1、PipelineService

145818_c3hG_947581.png

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风格”指的是什么?

145849_KbWX_947581.png

会话类

150001_9rux_947581.png

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都没啥关系,直接是框架调用的,汗!)

  1. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  2.             throws ServletException, IOException {
  3.         ......
  4.         try {
  5.             rundata = getRunData(request, response);
  6.  
  7.             if (!rundata.isRedirected()) {
  8.                 try {
  9.                     Profiler.enter("before request");
  10.  
  11.                     beforeRequest(rundata);
  12.                 } finally {
  13.                     Profiler.release();
  14.                     beforeRequestCalled = true;
  15.                 }
  16.  
  17.                 try {
  18.                     Profiler.enter("handle request");
  19.                     //pipeline的入口,处理的是try部分的pipeline
  20.                     handleRequest(rundata);
  21.                 } finally {
  22.                     Profiler.release();
  23.                 }
  24.             }
  25.         } catch (Throwable e) {
  26.             requestException = e;
  27.  
  28.             // 处理异常e的过程:
  29.             try {
  30.                 try {
  31.                     rundata.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  32.                 } catch (Exception ee) {
  33.                     // ignore this exception
  34.                 }
  35.  
  36.                 Profiler.enter("handle exception");
  37.  
  38.                 // 1. 处理的是catch部分的pipeline
  39.                 if (!handleException(rundata, request, response, e)) {
  40.                     try {
  41.                         // 2. 如果handleException放弃处理,则尝试生成简易的出错页面
  42.                         handleExceptionException(rundata, response, e, null);
  43.                     } catch (Throwable eee) {
  44.                         // 3. 放弃!把异常e交给servlet engine来处理
  45.                         handleHorribleException(e, eee);
  46.                     }
  47.                 }
  48.             } catch (Throwable ee) {
  49.                 try {
  50.                     if ((ee == e)
  51.                                 || ((ee instanceof ChainedThrowable)
  52.                                            && (((ChainedThrowable) ee).getCause() == e))) {
  53.                         // 确保e和ee不是指同一件事
  54.                         ee = null;
  55.                     }
  56.  
  57.                     if (ee != null) {
  58.                         log.error("Another exception occurred while handling exception "
  59.                                   + e.getClass().getName(), ee);
  60.                     }
  61.  
  62.                     // 2. 尝试生成简易的出错页面
  63.                     handleExceptionException(rundata, response, e, ee);
  64.                 } catch (Throwable eee) {
  65.                     // 3. 放弃!把异常e交给servlet engine来处理
  66.                     handleHorribleException(e, eee);
  67.                 }
  68.             } finally {
  69.                 Profiler.release();
  70.             }
  71.         } finally {
  72.             try {
  73.                 //提交rundata
  74.                 commitRunData(rundata);
  75.                 } catch (Exception e) {
  76.                 log.error("Exception occurred while commit rundata", e);
  77.             } finally {
  78.                 if (beforeRequestCalled) {
  79.                     try {
  80.                         Profiler.enter("after request");
  81.                         //处理的是final部分的pipeline
  82.                         afterRequest(rundata);           
  83.         }

  1. protected void handleRequest(RunData rundata) throws WebxException {
  2.         // 取得当前component的默认pipeline。
  3.         PipelineService pipelineService = (PipelineService) getWebxController().getServiceManager()
  4.                                                                 .getService(PipelineService.SERVICE_NAME,
  5.                                                                             rundata.getComponent());
  6.         Pipeline        pipeline = pipelineService.getPipeline();
  7.  
  8.         if (pipeline == null) {
  9.             throw new WebxException("Failed to get pipeline, the PipelineService may be failed to initialize");
  10.         }
  11.  
  12.         TryCatchFinallyValve tryCatchFinally = null;
  13.  
  14.         // 取得try-catch-finally valve,特殊处理之。
  15.         if ((pipeline.getLength() == 1) && pipeline.getValve(0instanceof TryCatchFinallyValve) {
  16.             tryCatchFinally = (TryCatchFinallyValve) pipeline.getValve(0);
  17.             rundata.setAttribute(TRY_CATCH_FINALLY_VALVE_KEY, tryCatchFinally);
  18.         }
  19.  
  20.         try {
  21.             if (tryCatchFinally == null) {
  22.                 pipeline.invoke(rundata);
  23.             } else {
  24.                 tryCatchFinally.invokeTryPipeline(rundata);
  25.             }
  26.         } catch (PipelineException e) {
  27.             throw new WebxException(e);
  28.         }
  29.     }

3、执行Pipeline

先是执行valve的invoke方法

tryCatchFinally.invokeTryPipeline(rundata)

再找到该valve的tryPipeline,执行该Pipeline,值得注意的是:Rundata被转型了~

tryPipeline.invoke(pipelineContext);

执行Pipeline,每次执行pipeline的时候,该pipeline自身维护一份上下文:pipelineContext,其实就是一个当前valve的状态而已罢了!

  1. public void invoke(PipelineContext pipelineContext) throws PipelineException {
  2.         // 清除step,重新开始。
  3.         setStep(pipelineContext, 0);
  4.  
  5.         // 执行下一步。
  6.         invokeNext(pipelineContext);
  7.     }

根据pipleline的valves(初始化的时候生成)和线程本地的pipelineContext去执行每个valve,每个valve在invoke的时候会抛出异常,直到被DefaultWebxControllServlet捕捉到,转而执行tryCatchFinally的catch子Pipeline

  1. public void invokeNext(PipelineContext pipelineContext)
  2.             throws PipelineException {
  3.         ValveForward forward = null;
  4.  
  5.         for (int step = getStep(pipelineContext); step < getLength();
  6.                     step = getStep(pipelineContext)) {
  7.             setStep(pipelineContext, step + 1);
  8.  
  9.             Valve valve = getValve(step);
  10.  
  11.             if (log.isDebugEnabled()) {
  12.                 String label = valve.getLabel();
  13.  
  14.                 if (StringUtil.isEmpty(label)) {
  15.                     log.debug("Entering valve[step=" + step + "]: "
  16.                         + getValve(step).getClass().getName());
  17.                 } else {
  18.                     log.debug("Entering valve[step=" + step + ", label=" + label + "]: "
  19.                         + getValve(step).getClass().getName());
  20.                 }
  21.             }
  22.  
  23.             try {
  24.                 //执行valve
  25.                 forward = valve.invoke(pipelineContext);
  26.             } finally {
  27.                 if (log.isDebugEnabled()) {
  28.                     String label = valve.getLabel();
  29.  
  30.                     if (StringUtil.isEmpty(label)) {
  31.                         log.debug("..exited valve[step=" + step + "]: "
  32.                             + getValve(step).getClass().getName());
  33.                     } else {
  34.                         log.debug("..exited valve[step=" + step + ", label=" + label + "]: "
  35.                             + getValve(step).getClass().getName());
  36.                     }
  37.                 }
  38.             }
  39.  
  40.             if (forward != null) {
  41.                 break;
  42.             }
  43.         }
  44.  
  45.         if (forward != null) {
  46.             log.debug("Forward to: " + forward);
  47.  
  48.             try {
  49.                 forward.invoke(this, pipelineContext);
  50.             } finally {
  51.                 log.debug("Forward returned: " + forward);
  52.             }
  53.         }
  54.     }

做转型,因为向下转型有风险,因此由此一步,既然valve执行过程中可能需要修改request和reponse,所以用到了rundata,但为什么不把出现pipelineContext的地方都改成rundata呢?这是又一个疑惑!

  1. public abstract class AbstractValve extends com.alibaba.webx.pipeline.AbstractValve {
  2.  
  3.     public ValveForward invoke(PipelineContext pipelineContext)
  4.             throws PipelineException {
  5.         RunData turbineRundata = null;
  6.  
  7.         try {
  8.             turbineRundata = (RunData) pipelineContext;
  9.         } catch (ClassCastException e) {
  10.             throw new PipelineException(RunData.class.getName() + " expected", e);
  11.         }
  12.  
  13.         return invoke(turbineRundata);
  14.     }

执行valve的invoke函数

  1. public ValveForward invoke(RunData rundata) throws PipelineException {
  2.     ......
  3.      Return null;
  4. }

下面看看遇到有goto和continue的valve是怎么搞的,举个goto的例子吧:

  1. public void invokeNext(PipelineContext pipelineContext)
  2.             throws PipelineException {
  3.         ValveForward forward = null;
  4.  
  5.         for (int step = getStep(pipelineContext); step < getLength();
  6.                     step = getStep(pipelineContext)) {
  7.             setStep(pipelineContext, step + 1);
  8.  
  9.            ......
  10.  
  11.             if (forward != null) {
  12.                 break;
  13.             }
  14.         }
  15.  
  16.         if (forward != null) {
  17.             log.debug("Forward to: " + forward);
  18.  
  19.             try {
  20.                 forward.invoke(this, pipelineContext);
  21.             } finally {
  22.                 log.debug("Forward returned: " + forward);
  23.             }
  24.         }
  25.     }

开始跑ValveForward的invoke方法,这里把pipeline和pipelineContext保存起来是很有必要的

  1. public ValveForward invoke(RunData rundata) throws PipelineException {
  2.         if (label == null) {
  3.             throw new PipelineException("Missing attribute \"goto\"");
  4.         }
  5.  
  6.         // 如果redirectTarget被设置,则继续执行,否则退出。
  7.         ValveForward gotoLabel      = null;
  8.         String       target         = rundata.getTarget();
  9.         String       redirectTarget = rundata.getRedirectTarget();
  10.  
  11.         if (StringUtil.isNotEmpty(redirectTarget) && !StringUtil.equals(target, redirectTarget)) {
  12.             rundata.setTarget(redirectTarget);
  13.             rundata.setRedirectTarget(null);
  14.  
  15.             gotoLabel = new GotoLabel(label);
  16.         }
  17.  
  18.         return gotoLabel;
  19.     }

开始修改context执行跳转

  1. public class GotoLabel implements ValveForward {
  2.     private String label;
  3.  
  4.     /**
  5.      * 创建<code>GotoLabel</code>对象。
  6.      *
  7.      * @param label label名,不能为空
  8.      */
  9.     public GotoLabel(String label) {
  10.         this.label = StringUtil.trimToNull(label);
  11.     }
  12.  
  13.     public void invoke(Pipeline pipeline, PipelineContext pipelineContext)
  14.             throws PipelineException {
  15.         if (this.label == null) {
  16.             throw new PipelineException("Label should not be empty");
  17.         }
  18.         //这里很明显是修改step,然后继续跑一个个的valve,仔细分析分析,还是有点意思的!尤其是这个invokeNext名字起的,
  19.         //要是在初次调用pipeline的invoke方法的时候见到这个invokeNext还真的不知道作者咋起这么个名字!
  20.         pipeline.gotoLabel(pipelineContext, label);
  21.         pipeline.invokeNext(pipelineContext);
  22.     }
  23. }


转载于:https://my.oschina.net/tryUcatchUfinallyU/blog/287946

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/823440
推荐阅读
相关标签
  

闽ICP备14008679号