Tomcat源码解析--启动流程

192 阅读3分钟

当你双击startup.bat,或者通过ide运行一个web项目时,Tomcat就开始了它漫长而又有序的启动过程。

打开startup可以看到,它仅仅是一个门面,负责调用真正的启动文件catalina.sh。

所以我们找到catalina.sh并打开,就会发现它带着一些参数去调用了Tomcat的启动类Bootstrap,然后在Bootstrap中加载各式各样的组件。

组件启动过程

先来看一张时序图:

可以看到整个启动流程是比较清晰的,它顺着server.xml中定义的组件层层启动,并且启动方式完全一致:init()--> start()。

ok,让我们把源码部署下来,启动Bootstrap看看。

public static void main(String args[]) {
        //首先判断Bootstrap daemon 是否为空,就是创建一个Bootstrap实例daemon
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init(); 
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            //指定默认的执行指令是start
            String command = "start"; 
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                //BootStrap的load方法,其实是在方法内部调用Calatina的load方法
                daemon.load(args); 
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                //setWait方法 主要是为了设置Server启动完成后是否进入等待状态的标志,如果为true则进入,否则不进入
                daemon.setAwait(true); 
                //load方法用于加载配置文件,创建并初始化Server
                daemon.load(args);
                //start方法用于启动服务器 
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

可以看到,首先调用的就是init(),我们将断点打在这里,可以看到它实例化了一个Catalina对象并赋值:

 if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass = catalinaLoader
            .loadClass("org.apache.catalina.startup.Catalina");
        ...

        catalinaDaemon = startupInstance;

第二个断点打在load(),可以看出它通过反射来调用了Catalina的load():

第三个断点找到Catalina.load(),可以看到它要调用Server的init():

找一下这个所谓的getServer(),发现其是一个StandServer对象:

问题就来了,StandardServer中并没有一个init(),我们只好查看它的父类:

直到LifecycleBase,我们才找到了所谓的init()。一个典型的模板方法模式,StandardServer只需要实现钩子initInternal()即可。

那么跟紧节奏,我们返回到StandardServer.initInternal(),可以看到它循环的调用了所有Service的init():

找到这个Service数组,哦原来是个StandardService。

是不是很眼熟,心里已经有猜测了,我们看一眼类图来确认想法。

对比Server,一模一样?后面的组件就不再跟进啦,是雷同的。

组件生命周期管理

Tomcat会逐步唤醒需要用到的每一个组件。那么,Tomcat是怎么做到有条不紊的管理呢?

通过上面的分析我们也知道了,LifeBase来管理了Server及以下所有组件的初始化,如果具体的Server有个性化行为,只需要重写initInternal()这个钩子方法即可。

为什么LifecycleBase这么玩?

其实很多架构源码都是这么做的,包括JDK的容器源码。

一个类,有一个接口,同时抽象一个抽象骨架类,把通用的实现放在抽象骨架类中,这样设计就方便组件的管理。

组件的结构

小知识:有没有发现,多数组件都是一个接口,然后有一个叫StandardXXX的实现类。 Engine、Host、Context、Wrapper它们各自的实现类分别是StandardEngine,StandardHost,StandardContext, StandardWrapper,他们都在tomcat的org.apache.catalina.core包下。

它们之间的关系,可以查看tomcat的server.xml也能明白(根据节点父子关系),并且他们四个Standard 的 container都是直接继承抽象类:org.apache.catalina.core.ContainerBase: