Tomcat源码分析(2)—Bootstrap启动流程


继上篇讲解完tomcat的整体结构与组件后,笔者接下来将分析tomat的启动过程,层层剖开tomcat启动时做了哪些事情、如何加载资源等等。

Bootstrap启动类

首先需要通过LifecycleState枚举类了解所有实现Lifecycle接口的组件拥有哪些周期状态,这对于后续了解组件的初始化、启动等流程是非常有帮助的。

  • NEW:刚创建组件实例时处于NEW状态。
  • INITIALIZING:组件正在初始化。
  • INITIALIZED:组件已经顺利地完成初始化。
  • STARTING_PREP:组件处于预启动状态。
  • STARTING:组件正处于启动中状态。
  • STARTED:组件已经成功启动。
  • STOPPING_PREP:组件处于预停止状态。
  • STOPPING:组件正在停止中。
  • STOPPED:组件已经正常停止。
  • DESTROYING:组件正在销毁中。
  • DESTROYED:组件已顺利地销毁。
  • FAILED:组件在初始化、启动、停止、销毁过程中出现失败。
 1public enum LifecycleState {
 2    NEW(false, null),
 3    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
 4    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
 5    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
 6    STARTING(true, Lifecycle.START_EVENT),
 7    STARTED(true, Lifecycle.AFTER_START_EVENT),
 8    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
 9    STOPPING(false, Lifecycle.STOP_EVENT),
10    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
11    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
12    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
13    FAILED(false, null);
14}

Bootstrap类是Catalina的启动加载器,该类将会构造一个类加载器用于加载Catalina内部的类(通过查找catalina.home的server目录下的所有jar包),加载完后便会执行容器的初始化,通过此种方法将Catalina内部类保持在系统类路径外,使得对应用程序类是不可见的,保证安全性。

Bootstrap

运行Bootstrap类的main(String[])方法就可以启动tomcat实例,其中主要步骤:

  • 创建Bootstrap实例并调用初始化init()方法。
    • 初始化类加载器并设置为当前线程的父加载器。
    • 预加载tomcat、javax包等自定义类。
    • 通过反射方式构建catalinaDaemon后台实例。
  • 根据命令行参数调用catalinaDaemon实例的load()方法。
    • 创建、初始化server.xml文件解析器Digester实例。
    • digester解析server.xml配置内容、并创建StandardServer实例。
    • 设置standardServer实例关联的catalina容器、设置catalina所在路径。
    • 调用init()方法初始化standardServer以及其所有子组件Service、Connector、Engine等。
  • 调用catalinaDaemon的start()方法启动服务器。
    • 判断catalinaDaemon后台实例是否完成初始化,若没有则重新实例化。
    • 反射调用catalinaDaemon的start()方法,依序启动Server实例及子组件Service、Connector、Engine,若启动过程中出现异常则调用destory()方法销毁所有组件。
    • 为catalinaDaemon注册钩子函数,保证standardServer、日志管理器LogManager实例能正确被关闭、销毁。

Bootstarp#main()

通过运行org.apache.catalina.startup.Bootstrap类的main方法即可一键启动tomcat,以下仅列出部分方法:

 1public final class Bootstrap {
 2		//省略部分代码...
 3  
 4    public static void main(String args[]) {
 5        synchronized (daemonLock) {
 6            if (daemon == null) {
 7                // Don't set daemon until init() has completed
 8                Bootstrap bootstrap = new Bootstrap();
 9                try {
10                    bootstrap.init();
11                } catch (Throwable t) {
12                    handleThrowable(t);
13                    t.printStackTrace();
14                    return;
15                }
16                daemon = bootstrap;
17            } else {
18                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
19            }
20        }
21        try {
22            String command = "start";
23            if (args.length > 0) {
24                command = args[args.length - 1];
25            }
26            if (command.equals("startd")) {
27                args[args.length - 1] = "start";
28                daemon.load(args);
29                daemon.start();
30            } else if (command.equals("stopd")) {
31                args[args.length - 1] = "stop";
32                daemon.stop();
33            } else if (command.equals("start")) {
34                daemon.setAwait(true);
35                daemon.load(args);
36                daemon.start();
37                if (null == daemon.getServer()) {
38                    System.exit(1);
39                }
40            } else if (command.equals("stop")) {
41                daemon.stopServer(args);
42            } else if (command.equals("configtest")) {
43                daemon.load(args);
44                if (null == daemon.getServer()) {
45                    System.exit(1);
46                }
47                System.exit(0);
48            } else {
49                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
50            }
51        } catch (Throwable t) {
52            // Unwrap the Exception for clearer error reporting
53            if (t instanceof InvocationTargetException &&t.getCause() != null) {
54                t = t.getCause();
55            }
56            handleThrowable(t);
57            t.printStackTrace();
58            System.exit(1);
59        }
60    }
61}

观察代码发现逻辑是非常简单的,在main方法中创建Bootstrap对象,再调用它的init()方法,然后根据启动时传入的参数调用boostrap对象的不同方法。

Bootstrap#init()

接下来我们看看init()方法的实现逻辑:

 1  public void init() throws Exception {
 2			//1、初始化类加载器
 3      initClassLoaders();
 4			//2、设置当前线程的上下文加载器为catalinaLoader
 5      Thread.currentThread().setContextClassLoader(catalinaLoader);
 6			//预先加载tomcat、javax包的自定义类
 7      SecurityClassLoad.securityClassLoad(catalinaLoader);
 8
 9      // Load our startup class and call its process() method
10      if (log.isDebugEnabled())
11        log.debug("Loading startup class");
12      Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
13      Object startupInstance = startupClass.getConstructor().newInstance();
14
15      // Set the shared extensions class loader
16      if (log.isDebugEnabled())
17        log.debug("Setting startup class properties");
18      String methodName = "setParentClassLoader";
19      Class<?> paramTypes[] = new Class[1];
20      paramTypes[0] = Class.forName("java.lang.ClassLoader");
21      Object paramValues[] = new Object[1];
22      paramValues[0] = sharedLoader;
23      Method method =
24        startupInstance.getClass().getMethod(methodName, paramTypes);
25      method.invoke(startupInstance, paramValues);
26
27      catalinaDaemon = startupInstance;
28  }

init()初始化的方法也相对的简单,首先调用initClassLoaders()初始化类加载器,使得tomcat可以加载应用程序类,接着设置当前线程的上下文加载器为CatalinaLoader。

 1    /**
 2     * Daemon reference.
 3     */
 4    private Object catalinaDaemon = null;
 5
 6    ClassLoader commonLoader = null;
 7    ClassLoader catalinaLoader = null;
 8    ClassLoader sharedLoader = null; 
 9
10		private void initClassLoaders() {
11        try {
12            commonLoader = createClassLoader("common", null);
13            if (commonLoader == null) {
14                // no config file, default to this loader - we might be in a 'single' env.
15                commonLoader = this.getClass().getClassLoader();
16            }
17            catalinaLoader = createClassLoader("server", commonLoader);
18            sharedLoader = createClassLoader("shared", commonLoader);
19        } catch (Throwable t) {
20            handleThrowable(t);
21            log.error("Class loader creation threw exception", t);
22            System.exit(1);
23        }
24    }

在initClassLoaders()初始化方法中可发现会创建三种类加载器并赋予成员变量,其中catalinaLoader与sharedLoader加载器的父加载器都是commonLoader。

在初始化完类加载器后,调用SecurityClassLoad.securityClassLoad方法预加载tomcat、javax包下的自定义类,防止使用securityManager时不会出现AccessControlException访问权限异常。

 1   public static void securityClassLoad(ClassLoader loader) throws Exception {
 2        securityClassLoad(loader, true);
 3    }
 4
 5    static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception {
 6        if (requireSecurityManager && System.getSecurityManager() == null) {
 7            return;
 8        }
 9        loadCorePackage(loader);
10        loadCoyotePackage(loader);
11        loadLoaderPackage(loader);
12        loadRealmPackage(loader);
13        loadServletsPackage(loader);
14        loadSessionPackage(loader);
15        loadUtilPackage(loader);
16        loadJavaxPackage(loader);
17        loadConnectorPackage(loader);
18        loadTomcatPackage(loader);
19    }

随后使用catalinaLoader加载器加载org.apache.catalina.startup.Catalina类,通过反射的方式实例化其对象,以及把sharedLoader作为参数反射调用setParentClassLoader方法,最后把实例化的Catalina实例赋予catalinaDaemon后台程序对象。

Catalina#load()

load()方法主要是完成StardardServer及子组件的初始化,以下省略部分代码,仅列出关键核心逻辑。

 1    public void load() {
 2        if (loaded) {
 3            return;
 4        }
 5        loaded = true;
 6        // Create and execute our Digester
 7        Digester digester = createStartDigester();
 8        InputSource inputSource = null;
 9        InputStream inputStream = null;
10        File file = null;
11        try {
12 						//省略部分代码
13            try {
14                inputSource.setByteStream(inputStream);
15                digester.push(this);
16                digester.parse(inputSource);
17            } catch (SAXParseException spe) {
18                log.warn("Catalina.start using " + getConfigFile() + ": " +
19                        spe.getMessage());
20                return;
21            } catch (Exception e) {
22                log.warn("Catalina.start using " + getConfigFile() + ": " , e);
23                return;
24            }
25        } finally {
26					//忽略部分代码
27        }
28        getServer().setCatalina(this);
29        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
30        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
31      
32        //standardServer初始化
33        try {
34            getServer().init();
35        } catch (LifecycleException e) {
36            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
37                throw new java.lang.Error(e);
38            } else {
39                log.error("Catalina.start", e);
40            }
41        }
42    }

从上面代码中可看到首先创建、初始化server.xml文件解析器Digester实例,使用digester解析server.xml配置、构建StandardServer实例。

随后关联standardServer实例与当前catalina实例,并设置当前catalina的根路径位置,最后调用init()方法完成standardServer的初始化。

在tomcat中的每个容器或组件中,使用了模板方法的设计模式,继承了LifecycleBase抽象类,因此在执行getServer().init();代码时会先调用父类的LifecycleBase#init()方法,而实际的初始化逻辑则交由子类完成,以下为LifecycleBase类的init()方法:

 1    @Override
 2    public final synchronized void init() throws LifecycleException {
 3        if (!state.equals(LifecycleState.NEW)) {
 4            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
 5        }
 6        try {
 7            setStateInternal(LifecycleState.INITIALIZING, null, false);
 8            initInternal();
 9            setStateInternal(LifecycleState.INITIALIZED, null, false);
10        } catch (Throwable t) {
11            handleSubClassException(t, "lifecycleBase.initFail", toString());
12        }
13    }
14    protected abstract void initInternal() throws LifecycleException;

从代码中可看到init()方法主要完成两件事情:

  • 更新当前standardServer实例生命周期状态为初始化中(LifecycleState.INITIALIZING),并触发生命周期监听器事件。
  • 执行initInternal()方法完成初始化。
  • 更新当前standardServer实例生命周期状态为已完成初始化(LifecycleState.INITIALIZED),并触发生命周期监听器事件。

在调用initInternal()方法时,交给子类standerServer#initInternal()方法完成初始化。

 1    @Override
 2    protected void initInternal() throws LifecycleException {
 3				//1、调用LifecycleMBeanBase#initInternal方法
 4        super.initInternal();
 5
 6      	//2、注册全局字符串缓存,如果有多个Server则也会注册多个字符串缓存对象
 7        onameStringCache = register(new StringCache(), "type=StringCache");
 8
 9        //3、注册MBeanFactory
10        MBeanFactory factory = new MBeanFactory();
11        factory.setContainer(this);
12        onameMBeanFactory = register(factory, "type=MBeanFactory");
13
14        //4、注册全局命名资源并初始化,该全局命名资源是配置在server.xml中
15        globalNamingResources.init();
16
17      	//5、使用catalina的父加载器校验系统JAR包是否包含MANIFEST文件等,此处忽略部分代码
18      
19        //6、初始化自定义的service
20        for (Service service : services) {
21            service.init();
22        }
23    }

代码的逻辑主要有以下几大步骤:

  1. 随后注册全局字符串缓存、MBeanFactory实例、全局命名资源实例(该实例是在解析server.xml时初始化的)。
  2. 使用catalina的父加载器校验系统JAR包是否包含MANIFEST文件以及添加该JAR文件到Manifest资源池中。
  3. 遍历该server实例关联的Service组件,并足一调用init()方法完成初始化。

以下为service组件的init()方法初始化逻辑,service组件的初始化逻辑主要做了以下事情:

  1. 完成Engine容器(org.apache.catalina.core.StandardEngine)的初始化。
    1. 创建指定Realm,若没有配置则创建默认的Realm。
  2. 遍历该service组件关联的Executor(org.apache.catalina.core.StandardThreadExecutor),并调用init()方法实例化。
  3. 关联mapper监听器(org.apache.catalina.mapper.MapperListener)初始化。
    1. 更新mapper监听器组件的生命周期状态为LifecycleState.STARTING。
    2. 查找并关联默认的虚拟主机Host。
    3. 关联该监听器到Engine容器及其所有子容器中。
    4. 注册Engine容器关联的所有虚拟主机Host及Host关联的Context、Wrapper子容器。
    5. 初始化所有关联的connector连接器。
 1@Override
 2protected void initInternal() throws LifecycleException {
 3
 4    super.initInternal();
 5
 6    if (engine != null) {
 7        engine.init();
 8    }
 9
10    // 初始化关联的Executor
11    for (Executor executor : findExecutors()) {
12        if (executor instanceof JmxEnabled) {
13            ((JmxEnabled) executor).setDomain(getDomain());
14        }
15        executor.init();
16    }
17
18    // 初始化mapper监听器
19    mapperListener.init();
20
21    // 初始化所有关联的connector连接器
22    synchronized (connectorsLock) {
23        for (Connector connector : connectors) {
24            try {
25                connector.init();
26            } catch (Exception e) {
27                String message = sm.getString(
28                        "standardService.connector.initFailed", connector);
29                log.error(message, e);
30
31                if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
32                    throw new LifecycleException(message);
33            }
34        }
35    }
36}

在整个service初始化过程中,connector连接器的初始化尤其重要,以下为connector连接器初始化代码:

  1. 创建并初始化Coyote适配器,关联至协议处理器。
  2. 检查parseBodyMethodsSet是否有值,若没则设置为POST方法。
  3. 判断是否需要APR的native库且APR协议处理器实例是否被创建,若都不满足则抛出异常。
  4. 判断是否需要APR本地库且APR协议处理器是否可用,若都不满足则抛出异常。
  5. 判断APR协议处理器是否可用且Apr协议处理器是否使用OpenSSL且协议处理器是否AbstractHttp11JsseProtocol类型
    1. 判断是否启用SSL且SSL实现类名为空,若满足则使用OpenSSLImplementation类名(OpenSSL与JSSE的配置兼容)。
    2. 否则执行步骤6。
  6. 协议处理器执行初始化(该协议处理器实例时在server.xml解析时创建的)。
 1@Override
 2protected void initInternal() throws LifecycleException {
 3    super.initInternal();
 4    // 1、创建并初始化Coyote适配器,并关联至协议处理器中
 5    adapter = new CoyoteAdapter(this);
 6    protocolHandler.setAdapter(adapter);
 7
 8    //2、确认parseBodyMethodsSet有默认值,parsetBodyMethods默认值为POST方法
 9    if (null == parseBodyMethodsSet) {
10        setParseBodyMethods(getParseBodyMethods());
11    }
12		//3、判断是否需要APR本地库 AND 判断Apr协议处理器实例是否被创建,若是Apr本地路且没创建Apr协议处理器则抛出异常
13    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
14        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
15                getProtocolHandlerClassName()));
16    }
17  	//4、判断是否需要APR/native库 AND Apr协议处理器是否可用,若是需要Apr本地库且没有Apr协议处理器可用则抛出异常
18    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
19        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
20                getProtocolHandlerClassName()));
21    }
22  	//5、判断Apr协议处理器是否可用 AND Apr协议处理器是否使用OpenSSL AND 协议处理器是否AbstractHttp11JsseProtocol类型
23    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
24            protocolHandler instanceof AbstractHttp11JsseProtocol) {
25        AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
26                (AbstractHttp11JsseProtocol<?>) protocolHandler;
27      	//5.1、如果SSL启用了且SSL实现类名为空,则使用OpenSSLImplementation类名
28        if (jsseProtocolHandler.isSSLEnabled() &&
29                jsseProtocolHandler.getSslImplementationName() == null) {
30            // 如果APR可用,可以使用OpenSSL,因为OpenSSL与JSSE的配置兼容。
31            jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
32        }
33    }
34
35    try {
36      	//协议处理器执行初始化(在server.xml解析时创建protocolHandler)
37        protocolHandler.init();
38    } catch (Exception e) {
39        throw new LifecycleException(
40                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
41    }
42}

关于协议AjpAprProtocol、Http11NioProtocol的初始化流程,笔者将在后续的协议组件文章中单独讲解。

Catalina#start()

在Bootstrap类的main()方法中,通过反射的方式执行catalina实例的start()方法,而start()方法主要作用是启动load()方法中初始化好的StandardServer实例,让服务器能监听端口并接收新的请求。(注意:以下代码块省略部分代码)

 1public void start() {
 2  	//判断server是否为空,为空则执行初始化
 3    if (getServer() == null) {
 4        load();
 5    }
 6		//若server为空,说明未能正确配置server.xml或者server初始化时异常
 7    if (getServer() == null) {
 8        log.fatal("Cannot start server. Server instance is not configured.");
 9        return;
10    }
11   	//此处省略部分代码
12  
13    try {
14        getServer().start();
15    } catch (LifecycleException e) {
16        log.fatal(sm.getString("catalina.serverStartFail"), e);
17        try {
18            getServer().destroy();
19        } catch (LifecycleException e1) {
20            log.debug("destroy() failed for failed Server ", e1);
21        }
22        return;
23    }
24
25		//此处忽略部分代码
26
27    // 注册关闭钩子
28    if (useShutdownHook) {
29        if (shutdownHook == null) {
30            shutdownHook = new CatalinaShutdownHook();
31        }
32        Runtime.getRuntime().addShutdownHook(shutdownHook);
33
34        // If JULI is being used, disable JULI's shutdown hook since
35        // shutdown hooks run in parallel and log messages may be lost
36        // if JULI's hook completes before the CatalinaShutdownHook()
37        LogManager logManager = LogManager.getLogManager();
38        if (logManager instanceof ClassLoaderLogManager) {
39            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
40                    false);
41        }
42    }
43
44    if (await) {
45      	//创建ServerSocker并监听端口
46        await();
47      	//停止standardServer实例
48        stop();
49    }
50}

观察上述代码块,发现其主要是做了以下事情:

  1. 判断server是否已初始化好,若是则调用server.start()方法启动服务器,否则直接返回。
    1. 触发状态为CONFIGURE_START_EVENT的事件监听器以及更新当前server的状态。
    2. 启动全局命名资源组件,该组件的启动逻辑主要是更新自身的生命周期状态。
    3. 遍历启动所有嵌套的server组件。
  2. 注册CatalinaShutdownHook关闭钩子,使得在关闭JVM时能销毁Catalina实例、日志管理器等组件。
  3. 创建关闭tomcat的ServerSocket实例并监听对应端口,等待接收关闭命令
  4. 关闭并销毁standardServer实例及其所有组件。

其中start()方法与initInternal()方法一样,也是使用模板方法设计模式,具体交由子类StandardServer实现。

 1
 2@Override
 3protected void startInternal() throws LifecycleException {
 4		//触发生命周期状态事件监听器
 5    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
 6  	//更新状态
 7    setState(LifecycleState.STARTING);
 8		//启动全局命名资源
 9    globalNamingResources.start();
10
11    //遍历启动所有service组件
12    synchronized (servicesLock) {
13        for (Service service : services) {
14            service.start();
15        }
16    }
17}

其中最重要的一步是遍历并启动嵌套的service、Engine容器、Executor执行器、MapperListener监听器、Connector连接器等组件。

  • 设置service自身生命周期状态,并启动engine容器。
    • 查找并启动engine容器关联的cluster集群组件。
    • 查找并启动engine容器关联的security realm。
    • 查找engine容器其所有子容器,使用线程池异步启动每个子容器,并等待返回启动结果。
    • 启动engine容器关联的PipeLine(Value阀门集合)。
    • 启动后台线程,专门用于定期检查session会话是否过期。
  • 启动绑定的Executor请求执行器。
    • 构建任务队列TaskQueue、自定义线程工厂TaskThreadFactory
    • 根据线程工厂、最小空闲线程、最大线程数、最大空闲时间等参数构建线程池ThreadPoolExecutor
    • 更新执行器自身的生命周期状态为LifecycleState.STARTING。
  • 启动service组件关联的mapper监听器
    • 查找并匹配Engine容器及其子容器绑定的虚拟主机Host。
    • 添加该监听器到Engine容器及子容器中。
    • 注册虚拟主机Host到Mapper映射规则实例中。
    • 注册Engine及其子容器绑定的虚拟主机下的所有上下文Contenxt。
  • 启动绑定的connector连接器
    • 启动connector绑定的协议处理器protocolHandler,包括socket连接处理、超时等等。
 1protected void startInternal() throws LifecycleException {
 2		//省略部分代码
 3    setState(LifecycleState.STARTING);
 4
 5    // 1、启动engine容器
 6    if (engine != null) {
 7        synchronized (engine) {
 8            engine.start();
 9        }
10    }
11		//2、启动Executor执行器
12    synchronized (executors) {
13        for (Executor executor: executors) {
14            executor.start();
15        }
16    }
17		//3、启动mapper监听器
18    mapperListener.start();
19
20    synchronized (connectorsLock) {
21        for (Connector connector: connectors) {
22            try {
23                // If it has already failed, don't try and start it
24                if (connector.getState() != LifecycleState.FAILED) {
25                  	//启动connector连接器
26                    connector.start();
27                }
28            } catch (Exception e) {
29                log.error(sm.getString("standardService.connector.startFailed",connector), e);
30            }
31        }
32    }
33}

上面主要是启动tomcat中各个组件、容器,这时候还需要创建等待关闭tomcat的ServerSocket以及它监听的端口,而await()方法的作用正是创建ServerSocket并等待接收关闭命令的。

  1@Override
  2public void await() {
  3  	//该处省略部分代码...
  4  
  5    //创建ServerSocker并监听端口
  6    try {
  7        awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));
  8    } catch (IOException e) {
  9        log.error("StandardServer.await: create[" + address+ ":" + port+ "]: ", e);
 10        return;
 11    }
 12
 13    try {
 14        awaitThread = Thread.currentThread();
 15        // 循环等待有效连接和命令
 16        while (!stopAwait) {
 17            ServerSocket serverSocket = awaitSocket;
 18            if (serverSocket == null) {
 19                break;
 20            }
 21            // 等待下一个连接
 22            Socket socket = null;
 23            StringBuilder command = new StringBuilder();
 24            try {
 25                InputStream stream;
 26                long acceptStartTime = System.currentTimeMillis();
 27                try {
 28                    socket = serverSocket.accept();
 29                    socket.setSoTimeout(10 * 1000);  // Ten seconds
 30                    stream = socket.getInputStream();
 31                } catch (SocketTimeoutException ste) {
 32                    log.warn(sm.getString("standardServer.accept.timeout",
 33                            Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
 34                    continue;
 35                } catch (AccessControlException ace) {
 36                    log.warn(sm.getString("standardServer.accept.security"), ace);
 37                    continue;
 38                } catch (IOException e) {
 39                    if (stopAwait) {
 40                        break;
 41                    }
 42                    log.error(sm.getString("standardServer.accept.error"), e);
 43                    break;
 44                }
 45
 46                // 从套接字读取一组字符
 47                int expected = 1024; // Cut off to avoid DoS attack
 48                while (expected < shutdown.length()) {
 49                    if (random == null)
 50                        random = new Random();
 51                    expected += (random.nextInt() % 1024);
 52                }
 53                while (expected > 0) {
 54                    int ch = -1;
 55                    try {
 56                        ch = stream.read();
 57                    } catch (IOException e) {
 58                        log.warn(sm.getString("standardServer.accept.readError"), e);
 59                        ch = -1;
 60                    }
 61                    // 若字符是控制字符或者EOF(-1)则终止循环
 62                    if (ch < 32 || ch == 127) {
 63                        break;
 64                    }
 65                    command.append((char) ch);
 66                    expected--;
 67                }
 68            } finally {
 69                // 完成操作后关闭socket
 70                try {
 71                    if (socket != null) {
 72                        socket.close();
 73                    }
 74                } catch (IOException e) {
 75                    // Ignore
 76                }
 77            }
 78
 79            // 判断命令内容是否SHUTDOWN
 80            boolean match = command.toString().equals(shutdown);
 81            if (match) {
 82                log.info(sm.getString("standardServer.shutdownViaPort"));
 83                break;
 84            } else
 85                log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
 86        }
 87    } finally {
 88        ServerSocket serverSocket = awaitSocket;
 89        awaitThread = null;
 90        awaitSocket = null;
 91        // 关闭ServerScoket
 92        if (serverSocket != null) {
 93            try {
 94                serverSocket.close();
 95            } catch (IOException e) {
 96              
 97            }
 98        }
 99    }
100}

最后总结

tomcat的启动入口是在Bootstrap的main方法上,在启动时流程步骤较多,但逻辑都相对清晰好理解,另外在代码上使用了模板方法、责任链设计模式,因此读者需要先自行熟悉此两种设计模式会更加好理解上面的讲解及代码。

整体上tomcat的启动首先需要初始化Server、Server、Engine、Host、Context、Executor、Connector等组件,接着再按照同样的顺序启动每个组件,当启动成功后便会监听端口,对外提供服务。