首页 > 代码库 > 【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)

【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)

首先,说明tomcat8和tomcat7的启动过程不一样,这篇是针对tomcat7的。

Tomcat启动的总过程

通过上面的介绍,我们总体上清楚了各个组件的生命周期的各个阶段具体都是如何运作的。接下来我们就来看看,Tomcat具体是如何一步步启动起来的。我们都知道任何Java程序都有一个main函数入口,Tomcat中的main入口是org.apache.catalina.startup.Bootstrap#main,下面我们就来分析一下它的代码:

org.apache.catalina.startup.Bootstrap#main

public static void main(String args[]) {

    if (daemon == null) {
        // Don‘t set daemon until init() has completed
        // 1 
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 2
            bootstrap.init();
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        // 3
        daemon = bootstrap;
    } else {
        // When running as a service the call to stop will be on a new
        // thread so make sure the correct class loader is used to prevent
        // a range of class not found exceptions.
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }

    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            // 4
            daemon.setAwait(true);
            daemon.load(args);
            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);
    }

}

下面我们逐一来分析一下上述代码中标注了数字的地方:

  1. 标注1的代码初始化了自举类的实例,标注2的代码对BootStrap实例进行了初始化,标注3的代码将实例赋值给了daemon。
  2. 标注4的代码首先调用了BootStrap的load方法,然后调用了start方法。

接下来我们分别分析一下BootStrap的init,load,start方法具体做了哪些工作。

BootStrap#init方法

首先来看org.apache.catalina.startup.Bootstrap#init方法,它的代码如下:

org.apache.catalina.startup.Bootstrap#init

public void init()throws Exception{

    // Set Catalina path
    setCatalinaHome();
    setCatalinaBase();

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    // 1
    Class<?> startupClass =
        catalinaLoader.loadClass
        ("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    // 2
    method.invoke(startupInstance, paramValues);
    // 3
    catalinaDaemon = startupInstance;

}

下面我们重点逐一来分析一下上述代码中标注了数字的地方:

  1. 标注1的代码通过反射实例化了org.apache.catalina.startup.Catalina类的实例;
  2. 标注2的代码调用了Catalina实例的setParentClassLoader方法设置了父亲ClassLoader,对于ClassLoader方面的内容,我们在本系列的后续文章再来看看。标注3的代码将Catalina实例赋值给了Bootstrap实例的catalinaDaemon.

BootStrap#load

接下来我们再来看看org.apache.catalina.startup.Bootstrap#load方法,通过查看源代码,我们知道此方法通过反射调用了org.apache.catalina.startup.Catalina#load方法,那我们就来看看Catalina的load方法,Catalina#load方法代码如下:

org.apache.catalina.startup.Catalina#load

public void load() {

    // 1 
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        file = configFile();
        inputStream = new FileInputStream(file);
        inputSource = new InputSource(file.toURI().toURL().toString());
    } catch (Exception e) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("catalina.configFail", file), e);
        }
    }



    try {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
    } catch (SAXParseException spe) {
        log.warn("Catalina.start using " + getConfigFile() + ": " +
                spe.getMessage());
        return;
    } catch (Exception e) {
        log.warn("Catalina.start using " + getConfigFile() + ": " , e);
        return;
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            // Ignore
        }
    }


    getServer().setCatalina(this);

    // Stream redirection
    initStreams();

    // Start the new server
    try {
        // 2
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error("Catalina.start", e);
        }

    }

}

上面的代码,我只保留了主流程核心的代码,下面我们重点逐一来分析一下上述代码中标注了数字的地方:

  1. 标注1的代码创建Digester实例解析”conf/server.xml”文件
  2. 标注2的代码最终调用了StandardServer的init方法。

大家可以自行查看下源代码,我们会发现如下的一个调用流程:

init call stack

org.apache.catalina.core.StandardServer#init
->org.apache.catalina.core.StandardService#init
-->org.apache.catalina.connector.Connector#init
-->org.apache.catalina.core.StandardEngine#init

因为StandardService,Connector,StandardEngine实现了LifeCycle接口,因此符合我们上文所获的生命周期的管理,最终都是通过他们自己实现的initInternal方法进行初始化

读到这里的时候,我想大家应该和我一样,以为StandardEngine#init方法会调用StandardHost#init方法,但是当我们查看StandardEngine#init方法的时候,发现并没有进行StandardHost的初始化,它到底做了什么呢?让我们来具体分析一下,我们首先拿StanderEngine的继承关系图来看下:通过上图以及前面说的LifeCyecle的模板方法模式,我们知道StandardEngine的初始化钩子方法initInternal方法最终调用了ContainerBase的initInternal方法,那我们拿ContainerBase#initInternal方法的代码看看:

org.apache.catalina.core.ContainerBase#initInternal

protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue =
        new LinkedBlockingQueue<Runnable>();
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

我们可以看到StandardEngine的初始化仅仅是创建了一个ThreadPoolExecutor,当看到这里的时候,笔者当时也纳闷了,StandardEngine#init竟然没有调用StandardHost#init方法,那么StandardHost的init方法是什么时候被调用的呢?遇到这种不知道到底方法怎么调用的时候怎么办呢?笔者介绍个方法给大家。我们现在需要知道StandardHost#init方法何时被调用的,而我们知道init最终会调用钩子的initInternal方法,因此这个时候,我们可以在StandardHost中override initInternal方法,增加了实现方法以后,有两种方法可以用,一种就是设置个断点debug一下就可以看出线程调用栈了,另外一种就是在新增的方法中打印出调用栈。笔者这里采用第二种方法,我们增加如下的initInternal方法到StandardHost中:

org.apache.catalina.core.StandardHost#initInternal

protected void initInternal() throws LifecycleException {
    Throwable ex = new Throwable();
    StackTraceElement[] stackElements = ex.getStackTrace();
    if (stackElements != null) {
        for (int i = stackElements.length - 1; i >= 0; i--) {
            System.out.print(stackElements[i].getClassName() + "\t");
            System.out.print(stackElements[i].getMethodName() + "\t");
            System.out.print(stackElements[i].getFileName() + "\t");
            System.out.println(stackElements[i].getLineNumber());
        }
    }
    super.initInternal();
}

上面的代码将会打印出方法调用堆栈,对于调试非常有用,上面的方法运行以后在控制台打印出了如下的堆栈信息:

stack info

java.lang.Thread    run  Thread.java   680
java.util.concurrent.ThreadPoolExecutor$Worker run  ThreadPoolExecutor.java   918
java.util.concurrent.ThreadPoolExecutor$Worker runTask  ThreadPoolExecutor.java   895
java.util.concurrent.FutureTask   run  FutureTask.java   138
java.util.concurrent.FutureTask$Sync   innerRun FutureTask.java   303
org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1549
org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1559
org.apache.catalina.util.LifecycleBase start    LifecycleBase.java    139
org.apache.catalina.util.LifecycleBase init LifecycleBase.java    102
org.apache.catalina.core.StandardHost  initInternal StandardHost.java 794

通过控制台的信息,我们看到是StartChild#call方法调用的,而我们查看StartChild#call方法其实是在StandardEngine的startInternal方法中通过异步线程池去初始化子容器。因此到这里我们就理清楚了,StarndardHost的init方法是在调用start方法的时候被初始化。那么接下来我们就来看看,start方法的整体调用流程。

BootStrap#start

采用分析load方法一样的方法,经过对BootStrap#start的分析,我们最终可以得到得到如下的调用链:

org.apache.catalina.startup.Bootstrap#start call stack

org.apache.catalina.startup.Bootstrap#start
->org.apache.catalina.startup.Catalina#start 通过反射调用
-->org.apache.catalina.core.StandardServer#start
--->org.apache.catalina.core.StandardService#start
---->org.apache.catalina.core.StandardEngine#start
---->org.apache.catalina.Executor#start
---->org.apache.catalina.connector.Connector#start

综合上文的描述我们总体得到如下的调用链:

org.apache.catalina.startup.Bootstrap#main call stack

org.apache.catalina.startup.Bootstrap#main
->org.apache.catalina.startup.Bootstrap#init
->org.apache.catalina.startup.Bootstrap#load
-->org.apache.catalina.startup.Catalina#load
--->org.apache.catalina.core.StandardServer#init
---->org.apache.catalina.core.StandardService#init
----->org.apache.catalina.connector.Connector#init
----->org.apache.catalina.core.StandardEngine#init
->org.apache.catalina.startup.Bootstrap#start
-->org.apache.catalina.startup.Catalina#start 通过反射调用
--->org.apache.catalina.core.StandardServer#start
---->org.apache.catalina.core.StandardService#start
----->org.apache.catalina.core.StandardEngine#start
----->org.apache.catalina.Executor#start
----->org.apache.catalina.connector.Connector#start

通过上面的分析我们已经搞清楚了Tomcat启动的总体的过程,但是有一些关键的步骤,我们还需要进行进一步的深入探究。let’s do it.

 

 

 

 

Reference

Tomcat启动过程(Tomcat源代码阅读系列之三)