webx2.0-PipelineService学习笔记

概述

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

webx2.0-PipelineService学习笔记_第1张图片

PipeLineService涉及到的类

服务类&实体类

1、PipelineService

webx2.0-PipelineService学习笔记_第2张图片

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

webx2.0-PipelineService学习笔记_第3张图片

会话类

webx2.0-PipelineService学习笔记_第4张图片

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);
    }
}


你可能感兴趣的:(webx)