Tomcat源码之-多层容器的设计

910 阅读3分钟

在阅读tomcat源码之前,有必要了解一下tomcat的多层容器设计,这样读起来会省很多力气。

tomcat容器

上图就是tomcat的基本结构,可以很明显的看出层层嵌套的架构设计。简易启动一个tomcat的代码如下:

    public static void main(String[] args) throws LifecycleException {
        Tomcat tomcat = new Tomcat();
        //设置路径
        tomcat.setBaseDir("d:tomcat/dir");

        Connector connector = new Connector();
        //设置端口
        connector.setPort(8080);
        tomcat.getService().addConnector(connector);

        Context context = new StandardContext();
        //设置context路径
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        tomcat.getHost().addChild(context);

        //添加servlet
        tomcat.addServlet("", "homeServlet", new HomeServlet());
        //设置servlet路径
        context.addServletMappingDecoded("/", "homeServlet");

        tomcat.start();
        tomcat.getServer().await();

    }

下面就来分析一下这段启动代码的逻辑。首先进入tomcat的start方法。

    public void start() throws LifecycleException {
        getServer();
        server.start();
    }

可以看到先是通过getServer方法判断是否存在server容器。如果不存在则新建。server容器就是结构图的最外层容器了。随后进入server的start方法。进入之后会发现server继承了LifecycleBase这个抽象类,而LifecycleBase实现了Lifecycle接口。这个接口就是控制容器生命周期的接口。查看接口方法可以看到有init,start和stop方法。我们先看看LifecycleBase的start方法

    public final synchronized void start() throws LifecycleException {
        ...
        if (state.equals(LifecycleState.NEW)) {
            init();
        }
        ...
        try {
            ...
            startInternal();
            ...
        } catch (Throwable t) {
            ...
        }
    }

可以看到在start方法中,根据容器的状态控制了容器的启动流程。在启动时候任一点出错的话,可以安全的退出。这样的话每个容器就只需要关注自己的init方法和startInternal方法就可以了。现在查看一下server的startInternal方法。

    protected void startInternal() throws LifecycleException {

        ...
        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
        ...
    }

可以看到server的startInternal方法主要任务就是启动service。这也正好是开始那个tomcat结构图的结构,外层控制内层容器的启动。接下来看看service容器的启动。直接查看StandardService的init方法和start方法。

    protected void initInternal() throws LifecycleException {
        if (engine != null) {
            engine.init();
        }
        ...
        mapperListener.init();
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                connector.init();
            }
        }
    }

在service的init方法中,首先初始化了engine,然后初始化mapper,最后初始化connector。这也很好理解,因为只有内部启动之后才能对外提供服务。

接下来先看看engine的启动,在看engine启动前,需要先了解下engine里面各层容器的作用。

Context 表示一个 Web 应用程序;Wrapper 表示一个 Servlet,一个 Web 应用程序中可能会有多个 Servlet;Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;Engine 表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。

他们对外提供服务的路由是这样的。

engine路由

在搞清楚这层关系之后,再看engine的启动就很简单了。engine管理的几个容器host,context和wrapper比之前多了一个继承就是 ContainerBase。他们的实现都继承了这个类。所以要研究的话直接看下这个类的实现。

在ContainerBase里,子类是以map的形式存在hash表中
protected final HashMap<String, Container> children = new HashMap<>();

看一下ContainerBase的start方法。

    protected synchronized void startInternal() throws LifecycleException {
        ...
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        MultiThrowable multiThrowable = null;

        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                ...
            }

        }
        ...
    }

可以看到在父容器的启动方法中,逐个的启动了子类容器。这样的话tomcat的大体启动流程就差不多了解了。接下来要做的就是根据具体功能,分析单个组件。