tomcat的入口位于Bootstrap.java,整个启动过程大致可以分为三步,

  • init 初始化catalina

  • load load参数

  • start 启动catalina

init过程

init过程首先会初始化一个Bootstrap对象,在Bootstrap的静态初始化块中会初始化catalinaHomeFile、catalinaBaseFile这两个全局变量,通常情况下这两个变量都是tomcat所在的目录。然后调用Bootstrap的init方法完成具体的init操作。

    public void init() throws Exception {

        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");
        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);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

上面的代码可以划分为如下的几步,

  1. 初始化类加载器 初始化的类加载器总共分为三类,commonLoader、catalinaLoader和sharedLoader。
  2. 通过类加载器catalinaLoader加载org.apache.catalina.startup.Catalina,通过反射初始化catalina对象
  3. 调用catalina的setParentClassLoader
  4. 设置catalinaDaemon为初始化好的catalina对象

load过程

load过程主要调用catalina的load方法完成。 load方法的主要过程分为如下的几步,

1.初始化digester

这个digester用于解析tomcat的server.xml文件

Digester digester = createStartDigester()
protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        // Initialize the digester
		。。。。。。

        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResourcesImpl");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResourcesImpl");

        digester.addObjectCreate("Server/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Listener");
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        digester.addSetProperties("Server/Service");
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");

		。。。。。。
        return (digester);

    }

2.解析server.xml

            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;
            }

Digester继承org.xml.sax.helpers.DefaultHandler,并且实现了startDocument, endDocument, startElement, endElement等用于解析xml的方法。下面拿startElement作为例子,

    @Override
    public void startElement(String namespaceURI, String localName, String qName, Attributes list)
            throws SAXException {
        boolean debug = log.isDebugEnabled();

        if (saxLog.isDebugEnabled()) {
            saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + qName + ")");
        }

		。。。。。。

        // Fire "begin" events for all relevant rules
        List<Rule> rules = getRules().match(namespaceURI, match);
        matches.push(rules);
        if ((rules != null) && (rules.size() > 0)) {
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire begin() for " + rule);
                    }
                    rule.begin(namespaceURI, name, list);
                } catch (Exception e) {
                    log.error("Begin event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("Begin event threw error", e);
                    throw e;
                }
            }
        } else {
            if (debug) {
                log.debug("  No rules found matching '" + match + "'.");
            }
        }

    }

这里主要是根据输入的namespaceURI和match获取对应的rules,然后遍历rules中的每一个rule,调用begin方法进行处理。而这里的rule就是在createStartDigester中注册的。比如下面的代码

        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

注册了三个rule,分别是ObjectCreateRule、SetPropertiesRule、SetNextRule,第一个rule用于创建StandardServer对象,第二个rule用于设置properties,第三个rule用于调用Catalina的setServer方法设置server为上面创建的StandardServer对象。

digeser通过调用parse方法进入server.xml的解析,并且初始化catalina中的各个属性。

3.设置重定向

    protected void initStreams() {
        // Replace System.out and System.err with a custom PrintStream
        System.setOut(new SystemLogHandler(System.out));
        System.setErr(new SystemLogHandler(System.err));
    }

tomcat对标准的输入输出做了封装,并且重新设置System.out和System.err

4.初始化server

        // 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();
            }
        }
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }

上面的代码可以划分为以下几步, 1. 设置Mbean供jmx查询 2. 调用catalina的classloader加载lib中的jar的manifest 3. 获取server中的Service,并且依次初始化

service的初始化依次调用engine、connector的init方法。而connector中由于定义了端口以及协议等信息,所以在connector初始化的时候还涉及到了端口绑定等一系列操作,如果此时端口被占用则会抛出异常。connector相关的东西这里暂时不展开,后续再做详细的介绍。

到这里系统的初始化过程基本结束,下面就要进入start阶段。而此时打印出的日志如下所示,

四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: CATALINA_BASE: D:\源码阅读\apache-tomcat-8.5.11-src 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: CATALINA_HOME: D:\源码阅读\apache-tomcat-8.5.11-src 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: Command line argument: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58750,suspend=y,server=n 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: Command line argument: -Dfile.encoding=UTF-8 四月 10, 2017 12:39:34 上午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent 信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jdk1.8.0_101\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Go\bin;C:\Program Files\Java\jdk1.8.0_101\bin;C:\Program Files\Java\jdk1.8.0_101\jre\bin;C:\Program Files (x86)\Git\bin;C:\Users\zblacker\AppData\Local\Microsoft\WindowsApps;C:\Users\zblacker\AppData\Local\atom\bin;C:\Program Files (x86)\Microsoft VS Code\bin;. 四月 10, 2017 12:39:37 上午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler [“http-nio-8080”] 四月 10, 2017 12:39:37 上午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 信息: Using a shared selector for servlet write/read 四月 10, 2017 12:39:38 上午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler [“ajp-nio-8009”] 四月 10, 2017 12:39:39 上午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 信息: Using a shared selector for servlet write/read 四月 10, 2017 12:39:40 上午 org.apache.catalina.startup.Catalina load 信息: Initialization processed in 6422 ms

start过程

start的过程主要调用StandardServer的start方法,而start方法的主要流程如下所示,

    @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

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

依次进入到各个service的start步骤,而service的start方法又依次会调用engine的start以及各个connector的start方法。首先来看一下engine的start方法,

    /**
     * Start this component and implement the requirements
     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        // Log our server identification information
        if(log.isInfoEnabled())
            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

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

engine的start方法仅仅是调用父类的start方法,父类的这个方法比较长,其中最重要的部分是

        // Start our child containers, if any
        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])));
        }

        boolean fail = false;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                fail = true;
            }

        }

上面的代码会寻找engine里面注册的子容器,并且调用线程池启动这些容器,此时一般情况下会有一个child container,该child container对应的实现是StandardHost。StandardHost的start方法最终也会进入到父类也就是ContainerBase的start方法中,与engine不同的是StandardHost中没有注册child container,因此上面的代码很快就会执行完毕进入到下面的代码,

        setState(LifecycleState.STARTING);

将StandardHost的生命状态设置为STARTING,

        this.state = state;
        String lifecycleEvent = state.getLifecycleEvent();
        if (lifecycleEvent != null) {
            fireLifecycleEvent(lifecycleEvent, data);
        }

并且根据lifecycleEvent的状态机获取下一个状态为start,同时根据状态start调用在StandardHost中注册的生命周期监听器,通常情况下这里的生命周期监听器包括HostConfig,该监听器根据传入的生命周期状态进入到对应的处理函数中,

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }

start()方法经过一系列处理后会进入到deployApps()方法进行应用的加载,

    protected void deployApps() {

        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

    }

appBase对应的就是配置的应用存放的目录,deployWARs会逐个加载appBase目录下的war包。deployWARs针对每一个应用做一系列校验,然后把启动的任务提交到线程池中,并发进行加载,这里不展开加载的具体过程了。 到此对应的应用加载完成,之后会进行一系列处理直到engine完全启动。之后会进行各个connector的启动,connector的启动主要做3件事情,

  1. 创建工作线程池
  2. 设置最大连接限制
  3. 根据配置的监听线程数启动监听线程

至此tomcat的启动基本完成。