欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[7]菜鸡写Tomcat之Container

程序员文章站 2022-07-14 10:46:48
...

上学期买了本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

[7]菜鸡写Tomcat之Container

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)组成了父子关系:

[7]菜鸡写Tomcat之Container

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

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

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

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

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

管道任务

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

[7]菜鸡写Tomcat之Container

管道(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);
    };

}

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

[7]菜鸡写Tomcat之Container

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

我的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;
    }

}