coding……
但行好事 莫问前程

Tomcat源码解读『Tomcat是怎么启动的』

上篇文章我们介绍了Tomcat的核心组件的的基本功能以及在Tomcat中的实现,但是这些组件类是如何被启动起来的?比如我们知道Context组件的功能是管理子容器Wraper的生命周期,那么在Context组件启动时,肯定要创建Wrapper实例并启动Wrapper。同理,我们知道Tomcat这么多组件,肯定有一个先后的启动过程(因为Tomcat的组件本来就是分层级的)。本篇文章我们就来介绍一下,Tomcat是怎么启动的。

使用过Tomcat的我们都知道,可以通过Tomcat的/bin目录下的脚本startup.sh来启动Tomcat,那么这个脚本肯定就是Tomcat的启动入口了,执行过这个脚本之后发生了什么呢?

  1. Tomcat本质上也是一个Java程序,因此startup.sh脚本会启动一个JVM来运行Tomcat的启动类 Bootstrap
  2. Bootstrap的主要任务是初始化Tomcat的类加载器,并且创建Catalina。关于Tomcat为什么需要自己的类加载器,我们后面再介绍
  3. Catalina是一个启动类,它通过解析server.xml,创建相应的组件,并调用 Server的start方法
  4. Server组件的职责就是管理Service组件,它会负责调用Service的start方法
  5. Service组件的职责就是管理连接器和顶层容器Engine,它会调用连接器和 Engine的start方法
  6. Engine组建负责启动管理子容器,通过调用Host的start方法,将Tomcat各层容器启动起来(这里是分层级的,上层容器管理下层容器)

1. Tomcat一键启停机制——LifeCycle

我们再来回顾一下Tomcat各组件之间的关系,很容易发现:如果想让Tomcat能够对外提供服务,我们需要创建、组装并启动这些组件;在服务停止的时候,我们还需要释放资源,销毁这些组件,因此这是一个动态的过程。也就是说,Tomcat 需要动态地管理这些组件的生命周期

Tomcat这么大一个框架,如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清晰?如何方便地添加或者删除组件?如何做到组件启动和停止不遗漏、不重复?

在回答上述问题之前,先来看看组件之间的关系,可以发现它们具有两层关系:

  • 组件有大有小,大组件管理小组件,比如Server管理Service,Service又管理连接器和容器
  • 组件有外有内,外层组件控制内层组件,比如连接器是外层组件,负责对外交流,外层组件调用内层组件完成业务功能。也就是说,请求的处理过程是由外层组件来驱动的

这两层关系决定了系统在创建组件时应该遵循一定的顺序:

  • 先创建子组件,再创建父组件,子组件需要被“注入”到父组件中
  • 先创建内层组件,再创建外层组件,内层组建需要被“注入”到外层组件

因此,最直观的做法就是将图上所有的组件按照先小后大、先内后外的顺序创建出来,然后组装在一起。但实际上这个思路其实很有问题,因为这样不仅会造成代码逻辑混乱和组件遗漏,而且也不利于后期的功能扩展

所以需要一个一种通用的、统一的方法来管理Tomcat各组件的生命周期,最好可以实现“一键启动”的效果。而Tomcat就是用LifeCycle实现了Tomcat一键启停的机制

1.1 什么是LifeCycle

文章最开始介绍的每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而每个具体组件的初始化方法,启动方法是不一样的。Tomcat把不变的抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle接口定义如下,每个具体的组件去实现这些方法。

public interface Lifecycle {
    // 添加监听器
    public void addLifecycleListener(LifecycleListener listener);
    // 获取所以监听器
    public LifecycleListener[] findLifecycleListeners();
    // 移除某个监听器
    public void removeLifecycleListener(LifecycleListener listener);
    // 初始化方法
    public void init() throws LifecycleException;
    // 启动方法
    public void start() throws LifecycleException;
    // 停止方法,和start对应
    public void stop() throws LifecycleException;
    // 销毁方法,和init对应
    public void destroy() throws LifecycleException;
    // 获取生命周期状态
    public LifecycleState getState();
    // 获取字符串类型的生命周期状态
    public String getStateName();
}

这里我们把LifeCycle接口定义分为两部分,一部分是组件的生命周期方法,比如init()、start()、stop()、destroy()。另一部分是扩展接口就是状态和监听器,关于状态和监听器,我们文章下面再介绍。

因为所有的组件都实现了LifeCycle接口,如果可以在父组件的init()方法里创建子组件并调用子组件的init()方法,在父组件的start()方法里调用子组件的start()方法,那么调用者就可以无差别的调用各组件的init()方法和start()方法,并且只要调用最顶层组件,也就是Server组件的init()和start()方法,整个Tomcat就被启动起来了

1.2 LifeCycleBase抽象类

有了LifeCycle接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。基类中往往会定义一些抽象方法,并调用这些方法来实现骨架逻辑,抽象方法是留给各个子类去实现,如下图所示:

LifeCycleBase实现了LifeCycle接口中所有的方法,还定义了相应的抽象方法交给具体子类去实现,这是典型的模板设计模式。

我们来简单看一下LifeCycleBase的init方法,如下:

  • 检查状态的合法性,比如当前状态必须是NEW然后才能进行初始化
  • 触发INITIALIZING事件的监听器,setStateInternal()方法中,会调用监听器的业务方法
  • 调用具体子类实现的抽象方法initInternal()方法。为了实现一键式启动,具体组件在实现initInternal()方法时,又会调用它的子组件的init()方法
  • 子组件初始化后,触发INITIALIZED事件的监听器,相应监听器的业务方法就会被调用

从而实现调用顶层组建的init方法就可以实现顶层及子组件的初始化,start()方法一个道理,这里不多介绍了。

1.3 LifeCycle扩展——事件监听机制

各个组件init()和start()方法的具体实现是复杂多变的,比如在Host容器的启动方法里需要扫描webapps目录下的Web 应用,创建相应的Context容器,如果将来需要增加新的逻辑,直接修改start()方法会违反开闭原则(为了扩展系统的功能, 你不能直接修改系统中已有的类,但是你可以定义新的类),那如何解决这个问题呢?

组件的init()和start()调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动。因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式。

这就是为什么LifeCycle接口中有两个添加和删除监听器的方法的原因。但除了上述监听器维护方法,我们还需要定义一些状态。所以Tomcat定义一个枚举LifeCycleState来表示组件有哪些状态。

可以看到组件的生命周期有NEW、INITIALIZING、INITIALIZED、STARTING_PREP、STARTING、STARTED等,而一旦组件到达相应的状态就触发相应的事件,比如 NEW状态表示组件刚刚被实例化。而当init()方法被调用时,状态就变成INITIALIZING 状态,这个时候,就会触发BEFORE_INIT_EVENT事件,如果有监听器在监听这个事件,它的方法就会被调用。

下面我们来看一下在上述init方法中触发事件监听的方法setStateInternal:

private synchronized void setStateInternal(LifecycleState state,
        Object data, boolean check) throws LifecycleException {

    if (log.isDebugEnabled()) {
        log.debug(sm.getString("lifecycleBase.setState", this, state));
    }

    if (check) {
        // Must have been triggered by one of the abstract methods (assume
        // code in this class is correct)
        // null is never a valid state
        if (state == null) {
            invalidTransition("null");
            // Unreachable code - here to stop eclipse complaining about
            // a possible NPE further down the method
            return;
        }

        // Any method can transition to failed
        // startInternal() permits STARTING_PREP to STARTING
        // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
        // STOPPING
        if (!(state == LifecycleState.FAILED ||
                (this.state == LifecycleState.STARTING_PREP &&
                        state == LifecycleState.STARTING) ||
                (this.state == LifecycleState.STOPPING_PREP &&
                        state == LifecycleState.STOPPING) ||
                (this.state == LifecycleState.FAILED &&
                        state == LifecycleState.STOPPING))) {
            // No other transition permitted
            invalidTransition(state.name());
        }
    }

    // 设置状态
    this.state = state;
    // 触发事件
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    // lifecycleListeners为监听器数组
    for (LifecycleListener listener : lifecycleListeners) {
        // 触发监听器的监听逻辑
        listener.lifecycleEvent(event);
    }
}

可以看到,通过setStateInternal方法,将LifeCycleBase的state成员变量设置为新的状态,并且触发了监听器的监听逻辑。那么什么时候、谁把监听器注册进来的呢?主要分为以下两种情况:

  • Tomcat自定义了一些监听器,这些监听器是父组件在创建子组件的过程中注册到子组件的。比如MemoryLeakTrackingListener监听器,用来检测 Context容器中的内存泄漏,这个监听器是Host容器在创建Context容器时注册到Context中的
  • 还可以在server.xml中定义自己的监听器,Tomcat在启动时会解析 server.xml,创建监听器并注册到容器组件

到这里我们可以得出结论:

  • Tomcat通过LifeCycle实现了一键启停(调用顶层组件的init方法就可以实现所有组件的初始化,调用顶层组件的start方法就可以完成所有组件的启动)
  • Tomcat通过LifeCycle事件监听机制,实现了对生命周期过程的优雅扩展
  • 另外LifeCycle还实现了一个状态机,管理LifeCycle的状态转化(具体在LifeCycleBase的init、start、stop、destroy方法中,有兴趣可以去看一下,代码比较简单),比如:
    • 当组件在STARTING_PREPSTARTINGSTARTED时,调用start()方法没有任何效果
    • 当组件在NEW状态时,调用start()方法会导致init()方法被立刻执行,随后start()方法被执行
    • 当组件在STOPPING_PREPSTOPPINGSTOPPED时,调用stop()方法没有任何效果
    • 当一个组件在NEW状态时,调用stop()方法会将组件状态变更为STOPPED,比较典型的场景就是组件启动失败,其子组件还没有启动。当一个组件停止的时候,它将尝试停止它下面的所有子组件,即使子组件还没有启动

2. Bootstrap启动入口

2.1 main()

本文最开始,我们介绍到,Tomcat是通过startup.sh调用了Bootstra的main方法启动的,这里我们来分析一下Bootstrap的main方法。

public static void main(String args[]) {

    synchronized (daemonLock) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 1. 初始化Bootstrap,主要包括实例化Tomcat特有的类加载器和Catalina实例
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            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")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        } 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方法初始化deamon成员变量,以及当接收到start命令时,调用deamon的load和start方法启动。

2.2 init()

下面来看一下Bootstrap的init方法:

public void init() throws Exception {
    // 1. 初始化Tomcat类加载器
    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");
    // 2. 实例化Catalina实例
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // 3. 反射调用Catalina的setParentClassLoader方法,将sharedLoader设置为Catalina的parentClassLoader成员变量
    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);
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}
  1. 创建commonLoader、catalinaLoader、sharedLoader类加载器(关于Tomcat的类加载器,后面文章单独介绍,这里我们把关注点放在Tomcat的启动上)
  2. 实例化org.apache.catalina.startup.Catalina对象,并赋值给静态成员 catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的 setParentClassLoader方法

这里如果不深入探讨Tomcat类加载器的话,我们可以简单理解为init方法,就是用来构造Catalina实例的。

2.3 load()

private void load(String[] arguments) throws Exception {

    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled()) {
        log.debug("Calling startup class " + method);
    }
    method.invoke(catalinaDaemon, param);
}

通过反射调用catalinaDaemon对象的load方法,catalinaDaemon对象就是在上面的init方法中实例化的。Catalina的load方法,我们下面再介绍,不过我们可以猜想,Catalina的load方法中应该会完成一些Tomcat组件的初始化操作。

2.4 start()

public void start() throws Exception {
    if (catalinaDaemon == null) {
        init();
    }

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}

start方法比较简单,就是通过反射调用catalinaDaemon对象上的start方法,该start方法,完成了Tomcat的启动。

3. Catalina

通过上面的介绍我们知道,Bootstrap的main方法中,就是调用了Catalina的load和start方法,从而完成了Tomcat的启动。这里我们来继续看一下这两个方法。

3.1 load()

public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // 1. 解析server.xml,实例化各Tomcat组件
    parseServerXml(true);
    Server s = getServer();
    if (s == null) {
        return;
    }

    // 2. 为Server组件实例设置Catalina相关成员value
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // 3. 调用Server组件的init方法,初始化Tomcat各组件
    try {
        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(sm.getString("catalina.initError"), e);
        }
    }

    if(log.isInfoEnabled()) {
        log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }
}

Catalina的init方法主要完成server.xml的解析以及Tomcat各组建的初始化。这里要着重强调的的有两点:

  • parseServerXml()方法完成了xml配置到Tomcat各组件的Java对象的转化,也就是说,通过调用该方法,我们之前介绍的Tomcat各组件都实例化出来了(这里parseServerXml()方法解析的细节以及关于使用web.xml配置Context,我们后面通过专门的文章再介绍)
  • getServer().init()方法,使用了我们上面介绍的LifeCycle机制,完成了Server及以下组件的初始化

3.2 start()

public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal(sm.getString("catalina.noServer"));
        return;
    }

    long t1 = System.nanoTime();

    // 1. start server
    try {
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    if (log.isInfoEnabled()) {
        log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
    }

    // Register shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    if (await) {
        await();
        stop();
    }
}

主要是调用Server组件的start方法,启动Tomcat的所有组件。

到这里我们知道,Tomcat是通过调用Catalina的load()和start()方法,完成了Tomcat的启动。load()方法负责组件的初始化,start()方法负责组件的启动。关于Tomcat组件的初始化,其中Host及其下级组件的初始化比较特殊,并没有在Engine的初始化方法中完成Host及下级组件的初始化。另外Context的实例化和启动过程也比较特殊(在上述parseServerXml方法中,并没有实例化,是在Host组件启动时动态生成的),另外我们知道Tomcat web应用支持热更新,也与Context的启动策略有关。关于Context实例化和初始化过程,我们先挖个坑,本篇文章中不做详细介绍,在后面用专门的文章详细介绍。

4. Server

这里我们着重来介绍一下Server组件的init和start方法。我们知道LifeCycle机制,init方法其实最终调用的是StandardServer的initInternal方法,start方法最终调用的是StandardServer的initInternal方法。

4.1 initInternal()

protected void initInternal() throws LifecycleException {

    // 1. 调用LifeCycleMBeanBase的initInternal方法,注册JMX Mbean
    super.initInternal();

    // Initialize utility executor
    reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
    register(utilityExecutor, "type=UtilityExecutor");

    // Register global String cache
    // Note although the cache is global, if there are multiple Servers
    // present in the JVM (may happen when embedding) then the same cache
    // will be registered under multiple names
    onameStringCache = register(new StringCache(), "type=StringCache");

    // Register the MBeanFactory
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    // Register the naming resources
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared
    // class loaders
    if (getCatalina() != null) {
        ClassLoader cl = getCatalina().getParentClassLoader();
        // Walk the class loader hierarchy. Stop at the system class loader.
        // This will add the shared (if present) and common class loaders
        while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
            if (cl instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                for (URL url : urls) {
                    if (url.getProtocol().equals("file")) {
                        try {
                            File f = new File (url.toURI());
                            if (f.isFile() &&
                                    f.getName().endsWith(".jar")) {
                                ExtensionValidator.addSystemResource(f);
                            }
                        } catch (URISyntaxException e) {
                            // Ignore
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }
    // 2. 调用所有子组件Service的init方法,初始化service
    for (Service service : services) {
        service.init();
    }
}

4.2 startInternal()

protected void startInternal() throws LifecycleException {

    // 1. 触发LifeCycle事件监听
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // 2. 启动所有Server的子组件Service
    synchronized (servicesLock) {
        for (Service service : services) {
            service.start();
        }
    }

    if (periodicEventDelay > 0) {
        monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
                new Runnable() {
                    @Override
                    public void run() {
                        startPeriodicLifecycleEvent();
                    }
                }, 0, 60, TimeUnit.SECONDS);
    }
}

5. Service

5.1 initInternal()

protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}
  1. 首先,往jmx中注册StandardService
  2. 初始化Engine
  3. 如果存在Executor线程池,进行init操作,这个Excecutor是tomcat的接口,继承至java.util.concurrent.Executor和org.apache.catalina.Lifecycle
  4. 初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化

5.2 startInternal()

protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);

    // Start our defined Container first
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

跟上述initInternal方法比较类似,只不过是调用各子组件(Engine、Connector)的start方法,启动子组件。

6. Engine

6.1 initInternal()

protected void initInternal() throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    getRealm();
    super.initInternal();
}

可以看到,Engine的init()初始化方法中,并没有去调用子组件Host的init方法去初始化Host,那么Host组件是在什么时候初始化的呢?答案是在Host的start方法中,在start方法中先进行Host组件的初始化,然后再进行启动操作

至于为什么没有在Engine的初始化方法中,初始化子组件Host。可能是出于效率考虑。Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加复杂耗时,在start阶段用多线程完成初始化以及start生命周期,效率可能会更高

6.2 startInternal()

protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    // Standard container startup
    super.startInternal();
}

直接调用父类ContainerBase的startInternal方法。

protected synchronized void startInternal() throws LifecycleException {
   
    // Start our subordinate components, if any
    logger = null;
    getLogger();
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    MultiThrowable multiThrowable = null;

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    setState(LifecycleState.STARTING);

    // Start our thread
    if (backgroundProcessorDelay > 0) {
        monitorFuture = Container.getService(ContainerBase.this).getServer()
                .getUtilityExecutor().scheduleWithFixedDelay(
                        new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
    }
}

可以看到,在ContainerBase中,使用了多线程来启动子组件。并且在最后通过setState(LifecycleState.STARTING)触发了事件监听。

这里启动子组件,使用了线程池,将子组件的启动任务提交到线程池。

private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

可以看到,线程池中执行的任务最终调用的还是子组件(Host)的start()方法。我们来看一下Host的start()方法,其实Host的start方法是继承自ContainerBase的,如下:

public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }

        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

可以看到,如果如果state为LifecycleState.NEW,会先进性初始化,然后在调用startInternal启动组件。这就是我们上面介绍的,Engine并没有在init方法中初始化子组件Host,而是在启动过程start方法中初始化的。

7. Host

7.1 initInternal()

protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreads());
    super.initInternal();
}

可以看到Host的init方法中也没有初始化子组件Context,Context组件的初始化跟Host组件初始化类似,也是跟随者Context的启动start过程初始化的。

7.2 startInternal()

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                    "standardHost.invalidErrorReportValveClass",
                    errorValve), t);
        }
    }
    super.startInternal();
}

可以看到,Host的start方法中,仅仅是为Host的piline添加了一些Valve,随后调用了父类ContainerBase的startInternal方法。虽然ContainerBase的startInternal方法中可以启动子组件,但好像server.xml中并么有配置Host子组件Context相关的信息,所以也没办法去启动Context组件。那么Context子组件是如何实例化,并初始化、启动的的呢?这个过程比较复杂,我们后面的文章单独介绍。

关于Tomcat的启动过程,我们先介绍到这,至于Context和Wrapper组件的初始化和启动过程,我们后面再专门介绍。本文,我们介绍了Tomcat如何实现了一键启停,主要介绍了LifeCycle机制和一些基础类的初始化启动过程,相信通过本文,大家可以对Tomcat的“一键启停”过程有一个大致的认识。

参考链接:

1. Tomcat源码

2. 《深入了解Tomcat&Jetty》

赞(0) 打赏
Zhuoli's Blog » Tomcat源码解读『Tomcat是怎么启动的』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址