【Tomcat】pipeline valve 设计模式

这里侧重设计模式的角度,具体结合tomcat细节的运行机制,准备在另一篇写。

只有先把这个模式抽出来理解清楚,再看tomcat的实现会更简单。

pipeline模式并不难理解,类似servlet规范中的filter。指的是一个pipeline,需要多个阀门串行处理,流水线作业。

那么很简单,我们可以写出如下的代码:

import java.util.ArrayList;
import java.util.List;

public class Pipeline {

    List<Valve> list = new ArrayList<Valve>();

    public Pipeline(){
        list.add(new Valve1());
        list.add(new Valve2());
        list.add(new Valve3());
        list.add(new Valve4());
    }

    public void invoke(){
        for(Valve valve : list){
            valve.invoke();
        }
    }

    public static void main(String args[]){
        new Pipeline().invoke();
    }
}

public interface Valve {
    void invoke();
}

public class Valve1 implements Valve {
    public void invoke() {
        System.out.println("valve1 invoke");
    }
}

其余valve类似。

上面的代码实现很简单,也可以完成pipeline的模型,既然如此,为什么还需要搞一个设计模式出来?

必然是上面的代码存在一定的问题:

没有抽象。分两层,一是对外提供服务,调用pipeline的使用方直接依赖了pipeline的具体实现,我们说软件设计一个重要的原则是“依赖倒置”,如果我们的功能是对外提供的,那么最好是以接口这种稳定的方式提供,这不是仅仅在pipeline模式中适用的,而是所有软件设计都应该重视的。依赖接口的好处是我们可以随意变更接口的实现,而依赖方不需要很大的修改。举个例子,上面的pipeline是基于list实现的,现在我想要添加一个array实现的,而且调用方在某种情况下需要调用array实现的,那么我们必须新建一个ArrayPipeline的类,然后在需要调用array的地方把创建pipeline实例的方式改了,调用方式也有可能改。如果我们使用了接口,那么任何调用方的代码不需要需改,只需要修改接口的赋值语句。如果使用了spring,那么只需要修改一个bean的定义文件即可。二是对内实现上,上面的实现pipeline和valve是未解耦的,问题和对外的一样,需要使用接口解耦,其次pipeline的构建是硬编码的,事实上,我么很有可能需要动态添加valve,因此pipeline需要有addValve之类的接口。

综上,上面的代码最大问题是没有解耦,在之后很难做扩展。因此需要抽象出每一种角色的职责。这其实也是所有设计模式出现的初衷,设计模式很大程度上就是为了抽象,让实现以接口的方式进行。

重新设计:

public interface Pipeline {
    void setBasic(Valve valve);
    void add(Valve valve);
    void remove(Valve valve);
    void invoke();
}

import java.util.ArrayList;
import java.util.List;

public class StandardPipeline implements Pipeline {
    List<Valve> valves = new ArrayList<Valve>();
    Valve basic;

    public void setBasic(Valve valve) {
        basic = valve;
    }

    public void add(Valve valve) {
        valves.add(valve);
    }

    public void remove(Valve valve) {
        valves.remove(valve);
    }

    public void invoke() {
        for(Valve valve : valves){
            valve.invoke();
        }
    }
}

抽象出了valve和pipeline的职责,就形成了上面的代码。

tomcat的pipeline规定必须有有个basic的valve,该valve是该流水线的最终处理器,完成这条流水线的主要职责,basic前可以没有valve,但是basic是必须存在的。就好比可以没有前面的filter但是必须有最后的serlvet来处理一个http请求。

这里的pipeline的实现是一种for循环方式,如果每一个valve可以决定是否打断当前的执行链,这种for循环就无法处理了。当然,我们可以让每一个valve返回一个boolean值,但是这种方式代价高昂,前提是valve本省没有返回值。如果是类似linux管道的模式,每一个valve要处理上一个valve的返回值,那么这种方式就不可行。而且,从逻辑上讲,是否往下执行,是在一个valve内部需要解决的事情,pipeline不应该干涉。

所以上面for循环的方式假定了每一个valve都必须执行一次,没有打断执行链的能力。

为了更优雅的解决这个问题,tomcat的pipeline模式加入了一个新的角色context。那么最后的设计变成了这样:

public interface PipelineContext {
    void invokeNext();
}


public interface Valve {
    void invoke(PipelineContext context);
}

public class Valve1 implements Valve {
    public void invoke(PipelineContext context) {
        System.out.println("valve1 invoke");
        context.invokeNext();
    }
}

import java.util.ArrayList;
import java.util.List;

public class StandardPipeline implements Pipeline {
    List<Valve> valves;
    Valve basic;
    PipelineContext context;

    public StandardPipeline(){
        valves = new ArrayList<Valve>();
        context = new StandardPipelineContext();
    }

    public void setBasic(Valve valve) {
        basic = valve;
    }

    public void add(Valve valve) {
        valves.add(valve);
    }

    public void remove(Valve valve) {
        valves.remove(valve);
    }

    public void invoke() {
        context.invokeNext();
    }

    private class StandardPipelineContext implements PipelineContext{

        int step = 0;

        public void invokeNext() {
            if(!valves.isEmpty() && step < valves.size())
                valves.get(step++).invoke(context);
            else
                basic.invoke(context);
        }
    }

    public static void main(String args[]){
        Pipeline pipeline = new StandardPipeline();
        pipeline.setBasic(new BasicValve());
        pipeline.add(new Valve1());
        pipeline.add(new Valve2());
        pipeline.add(new Valve3());
        pipeline.add(new Valve4());

        pipeline.invoke();
    }
}

取消了pipeline里invoke方法的for循环。添加了一个context的角色。之前说每一个valve要有决定是否打断链子的能力,那它必须获取到一些全局的信息才行,这里的context就是为了让valve具有这种打断能力。context提供了valve执行下一个valve的能力,这样valve在内部就可以通过if来判断是否需要执行下一个valve。

context本身的实现像是一个游标,提供了一个接口来滑动游标。这样,pipeline里invoke方法就像是一个迭代器模式,没有for循环。

输出:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值