[7]菜鸡写Tomcat之Container

上学期买了本How Tomcat Works然后一直丢在书柜里没看,之前有一天闲,翻出来看了几页,觉得挺有趣的,所以就跟着书上的思路和一些tomcat源码,自己写了一个简单的应用服务器—Tiny Server

感觉在这个过程中学到了不少东西,所以想把自己的思路和想法写出来分享一下

Ps:因为是自己的理解,所以如有不对请各位大佬指出,感激不尽

我写的Tiny Server的源码在这里:https://github.com/Lehr130/TinyServer

整个项目并没有完全按照tomcat的写法来,只是按照自己的理解去实现的,而且有些功能并没有完全实现,还有待各位指教

相关内容

[7]菜鸡写Tomcat之Container
[6]菜鸡写Tomcat之Cookie与Session
[5]菜鸡写Tomcat之WebClassloader
[4]菜鸡写Tomcat之生命周期控制
[3]菜鸡写Tomcat之Filter
[2]菜鸡写Tomcat之Context
[1]菜鸡写Tomcat之Wrapper

什么是Servlet容器

Tomcat的结构

Tomcat大体可以分为两个部分:Connector和Container

image-20200302100140630

Tomcat中一个完整的Service服务是由Connector和Container组成的,首先,Connector接受请求并封装,然后按照请求,找到对应的Container容器,执行这个容器里的任务。

仔细观察可以发现,Connector和Container的对应关系是多对一,一个Service中只能有一个Container

在Connector部分,我们只需要去关心怎么样处理请求,接受Http请求或者AJP请求,然后,我们把封装成为ServletRequest的请求统一交给Container处理

Container的职能

一个JavaWeb项目是由若干个Servlet小程序组成的,项目的每个部分都需要被放到容器里

tomcat中用了四个容器:Wrapper、Context、Host和Engine来管理不同层级的资源

其中:

  • Wrapper容器就管理了一个Servlet小程序和他所有的相关信息,除了Servlet以外不能有其他子容器了

  • Context容器就管理了一个JavaWeb项目和他的各种配置文件参数,有多个Wrapper作为子容器

  • Host容器就代表了一个虚拟主机,可以配置多个Context作为子容器

  • Engine则代表了整个Tomcat引擎,名为Catalina,可以有多个Host子容器,但是Engine本身有且只有一个

部署的时候,我们至少需要有一个Wrapper容器,就能让一个Servlet程序正常工作了

但是如果需要同时调用多个Servlet,则需要用Context…需要匹配多个主机,则准备多个Host…

Container接口

这几个容器通过关联的方式(类似于Classloader)组成了父子关系:

image-20200302111430491

无论是哪个层级的容器,他们在管理的过程中都会有类似的操作,比如添加子容器,准备管道任务等等,同时,每个容器还会有自己的一些特有的组件,比如类加载器,安全管理器等等,所以,他们需要统一地实现一个叫做Container的接口

接口需要做的事情大概有这样几件事:

  • 能够执行自己对应的任务
  • 支持对子容器的管理

在Container中,执行自己层级的任务时,会调用到invoke接口,invoke接口则会把请求发送给容器内部一个叫做管道任务的组件来执行

在具体谈Container之前,我们需要了解Tomcat中管道任务的设计思路

管道任务

Tomcat在设计每个容器invoke时要执行的任务的时候,采用了责任链的设计模式,也就是说,一个容器内维持着一条管道,管道由多个阀组成,其中每一个阀就是一个任务,如果我们需要执行这个容器的invoke方法,那么任务就会像水流过管道一样,在每个阀中触发对应的任务执行相应的操作,不了解的读者可以用FilterChain来类比

image-20200302112709903

管道(Pipline)和阀(Valve)

管道其实就类似一个任务表,如果容器在被触发的时候,需要先后按照某种顺序完成一些特定的动作,例如先进行日志记录,然后再进行安全检查,然后才触发Servlet的service方法执行业务逻辑,则我们可以把这一系列的任务抽象封装成一个叫做阀(Valve)的组件,按照你希望的执行顺序,依次放入管道中排列好,一旦触发请求,则管道里排放的任务就能够一个接着一个执行了

管道的实现代码简化后类似于这样:

public class TommyPipeline {

    private List<TommyValve> valveList = new ArrayList<>();

    private TommyValve basicValve;

    public void addValve(TommyValve valve)
    {
        valveList.add(valve);
    }

    public void invoke(MyRequest req, MyResponse res)
    {
        //逐个invoke
        valveList.forEach(v->v.invoke(req,res));
        //执行基础阀
        basicValve.invoke(req,res);
    }

    public void setBasicValve(TommyValve basic)
    {
        this.basicValve = basic;
    }

  

逻辑很简单,内部维护了一个代表任务的valve的list(在原版Tomcat中是用数组来实现的)

提供了valve阀的添加方法和一个叫做基础阀的东西的设置方法

在执行invoke触发操作的时候,管道会逐个触发Valve阀中的任务,最后触发基础阀中的任务

这里需要注意一下的就是,管道任务只会顺序执行一次,而不会像FilterChain一样又反着执行回来

现在来看一下Valve阀门这个东西的代码:

public interface TommyValve {

    /**
     * 每个阀的执行方法,必须把方法返回结果放到res里面去
     * @param req
     * @param res
     */
    void invoke(MyRequest req, MyResponse res);

}

“阀”实际上就是一个接口,如果想要放入到管道里去执行的话必须就要实现这个invoke方法,在invoke方法中写入你想执行的业务逻辑(这倒是有点策略模式的感觉…)

上文说到的基础阀就是这个接口的一个实现类

阀的使用方法也很简单粗暴,比如说,我们想在容器执行任务的时候先输出一句"Hello",那么我们就写一个叫做HelloValve的类去实现这个Valve接口,然后在invoke方法里写一句sout(“Hello”),然后把这个阀的实现类通过管道pipline的add方法加入进去就好了

基础阀(Basic Valve)

基础阀,就是整个容器的任务的核心逻辑了

实现类

比如对于管理Servlet级别的Wrapper容器,他被调用的时候本质就是要触发Servlet的service方法,所以,我们会把service方法的调用过程单独放到一个实现了Valve接口的类里去,然后把这个对象放入到当前容器所包含的最后一环去,当前面杂七杂八的什么日志任务,安全检查任务完成后,就会正式触发service方法,servlet程序就开始执行他自己的工作然后把响应发回去了

在Tomcat中,他是为每一个容器单独写了一个对应的基础阀的类的,我们以Wrapper为例:

在Wrapper中,他的管道任务的最后一个环节基础阀放入的对象是一个叫做WrapperValve的类,这个类的代码大致如下:

public class WrapperValve implements TommyValve {

    .....各种set get之类的
    void invoke(MyRequest req, MyResponse res)
    {
        servlet = allocate();
        servlet.service();
    };

}

对于高级容器

我们以Context容器为例,Context容器最核心的业务逻辑就是:通过uri去找到对应的Wrapper,然后触发这个wrapper的invoke方法,封装到一个阀里以后:

public class ContextValve implements TommyValve {

    .....各种set get之类的
    void invoke(MyRequest req, MyResponse res)
    {
        Wrapper wrapper = req.getWrapper();
        wrapper.invoke(req,res);
    };

}

这倒是没什么,不过我们从整体结构上看,当有多层容器的时候,执行任务的流程大概是这样:

image-20200302121035584

好了我尽力了,有嘴讲不清,,,以后再来修改,,,,

我的TinyServer里的实现代码

TinyServer是我个人在读tomcat源码的时候模仿着写的一个非常简单的玩具,所以很多设计结构上都比tomcat简陋很多,由于上文是用到了自己的代码来做的分享,所以这里附上我对于Wrapper级容器的实现的源码,完整项目见https://github.com/Lehr130/TinyServer

我直接把原版中Container接口和ContainerBase类rua到一起,写成了一个抽象类,然后把每个容器对应的基础阀直接改造成了内部类来处理,代码如下:

package tiny.lehr.tomcat.container;

import tiny.lehr.bean.MyRequest;
import tiny.lehr.bean.MyResponse;
import tiny.lehr.tomcat.TommyPipeline;
import tiny.lehr.tomcat.lifecircle.*;
import tiny.lehr.tomcat.valve.TommyValve;

import java.util.List;


/**
 * @author Lehr
 * @create 2020-01-16
 * 模仿Tomcat的那个Container设计
 * 
 * <p>
 * 是个标识性接口
 */
public abstract class TommyContainer implements TommyLifecycle {


    //一个监听器管理器???
    private TommyLifecycleSupport lifecycle = new TommyLifecycleSupport(this);

    private TommyPipeline pipeline = new TommyPipeline();

    protected TommyLifecycleSupport getLifecycle() {
        return lifecycle;
    }

    /**
     *
     *
     * @param req
     * @param res
     */
    public void invoke(MyRequest req, MyResponse res) {
        addBasicValve();
        pipeline.invoke(req, res);
    }


    /**
     * 
     *
     * @param valve
     */
    public void addValve(TommyValve valve) {
        pipeline.addValve(valve);
    }

    public void addBasicValve() {
        pipeline.setBasicValve(new BasicValve());
    }





    /**
     * 定义了基础阀的工作过程
     *
     * @param req
     * @param res
     */
    protected abstract void basicValveInvoke(MyRequest req, MyResponse res);

    /**
     * 
     */
    class BasicValve implements TommyValve {

        @Override
        public void invoke(MyRequest req, MyResponse res) {
            basicValveInvoke(req, res);
        }
    }


    @Override
    public void addLifecycleListener(TommyLifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }


    @Override
    public List<TommyLifecycleListener> findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    @Override
    public void removeLifecycleListener(TommyLifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }


    private Boolean started = false;


    //子类具体要做的
    protected abstract void doStart() throws Exception;

    protected void beforeStart(){

        if(this instanceof TommyWrapper)
        {
            addLifecycleListener(new TommyWrapperLifecycleListener());
        }
        if(this instanceof TommyContext)
        {
            addLifecycleListener(new TommyContextLifecycleListener());
        }
        if(this instanceof TommyHost)
        {
            addLifecycleListener(new TommyHostLifecycleListener());
        }
        if(this instanceof TommyEngine)
        {
            addLifecycleListener(new TommyEngineLifecycleListener());
        }

    }

    //因为接口不能用synchronized  所以要具体实现过来
    @Override
    public synchronized void start(){

        if (started) {
            System.out.println("md都开始了还搞锤子");
            return;
        }

        //我设计这个是为了一开始好添加监听器的
        beforeStart();

        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,null);

        started = true;

        //我这里又抽象了一次哈
        try {
            doStart();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //启动阀门
        pipeline.start();

        //通知所有监听器
        lifecycle.fireLifecycleEvent(START_EVENT, null);


        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

    }


    protected abstract void doStop();


    @Override
    public synchronized void stop(){

        if (!started) {
            System.out.println("md都结束了还搞锤子");
        }



        lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT,null);

        lifecycle.fireLifecycleEvent(STOP_EVENT,null);

        started = false;

        pipeline.stop();

        //停止子容器  但是我不知道为什么之而立是先stop event再去停止
        doStop();

        //TODO: 这里还有报错需要改进

        lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    }


    public Boolean isStarted()
    {
        return  started;
    }

}

发布了47 篇原创文章 · 获赞 105 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43948583/article/details/104618564