Tomcat的生命周期(二)

前言
本文是对Tomcat生命周期内容进行扩展和强化的第一篇文章,在上一篇文章中以StandarServer为例,从宏观上分析了容器的生命周期流转过程,分析了LifeEvent的设计思想,在此基础上,本文着重于分析StandardService下所有Container相关子容器的初始化和启动过程。
文中涉及的很多知识已在前面的文章中做了铺垫,重复的内容就不在累述。建议读者按着Tomcat系列文章的顺序阅读下来,否则可能会造成一定的理解困难,在遇到之前已经提及过的知识点时,本文都会以“在某某文章中说过某某”类似的语句进行提醒

在Tomcat架构中各个组件和组件间关系(二)中曾经提过,Tomcat从整体架构上可以分为两大部分:监听请求并生成对应RequestResponseConnector连接器,以及处理请求和控制Tomcat容器运转的Container。再联系上篇生命周期文章中图9中对应的三大部分,我们以container.init()connector.init()两者作为切入点,开始对组件的初始化进行分析(第二部分executor.init(),因为默认是不配置连接池的,所以可以认为该部分无效),分析入口如下所示

Tomcat的生命周期(二)_第1张图片
图1. StandardService中两处分析入口

前文中提到过 Container的顶层容器为 StandardEngine,结合模板方法的设计可知, container.init()最终会调用 StandardEngine.initInternal()
Tomcat的生命周期(二)_第2张图片
图2. StandardEngine的initInternal

其中 getRealm()主要用于获取在 server.xml上配置的 域对象,而域对象的作用之前也说过,主要用于安全性认证。除此之外就剩下简单调用父类的 initInternal()。看到这里有些读者可能会产生疑惑,之前说过容器间的初始化是“父传子”,“子传孙”的责任链模式,怎么刚到 StandardEngine就断了呢?其实可以这么理解,责任链开始必定是由外到内的过程,当最内层执行完一定返回上一层,也就是再经历由内到外的逆向过程,我们来看看在逆向的过程中发生了什么(暂且忽略 StandardServiceinitInternal()的剩下部分)
Tomcat的生命周期(二)_第3张图片
图3. LifecycleBase中init()

初始化方法的最初入口在 Catalina类中的 load()load()会调用 getServer().init(),最终对应 LifecycleBaseinit(),如上图所示,这里的 initInternal()就是责任链的入口,当返回时会设置初始化结束生命周期状态 LifecycleState.INITIALIZED,对应的生命周期事件为 Lifecycle.AFTER_INIT_EVENT
Tomcat的生命周期(二)_第4张图片
图4. LifecycleBase的setStateInternal

在设置生命周期状态的同时会发布对应的生命周期状态给对该事件“感兴趣”的监听器,我们看看哪些监听器会对这里的 Lifecycle.AFTER_INIT_EVENT做出响应。在 Tomcat架构中各个组件及组件间关系(二)中的图20,有关 ContextConfig有关介绍时提过,该监听器会对初始化结束事件作出相应,主要工作为初始化解析两种 web.xml文件的解析器 webDigesterwebFragmentDigester,为接下来的启动事件解析 web.xml做准备。至此 Container容器的初始化工作结束,回到图1中开始分析 Connectorinit()方法
Tomcat的生命周期(二)_第5张图片
图5. Connector的initInternal()

Connector的初始化过程同样遵循之前所说的“模板方法”设计模式,最终会走到上图中 Connector自身实现的 initInternal(),方法中 CoyoteAdapter可以理解为 ConnectorContainer之间的桥梁,也就是说将 Connector中接收的请求交给 Container一系列容器处理的流程就是该类负责的,这里将当前的 Connector实例通过构造器传递给了 CoyoteAdapter,又因为 ConnectorStandardService存在双向关联关系,那么我们就可以在 CoyoteAdapter中得到 Connector对应的 StandardService,进而得到 StandardService下的 StandardEngine,并将 request交于一系列容器进行处理,具体的代码下文讲对应流程时会看到,由于在整个初始化和启动流程中类与类之间的关系比较复杂,因此,我按照分析的关键功能画了一张大致的类图,有助于下面的理解和分析
Tomcat的生命周期(二)_第6张图片
图6. 关键流程涉及类UML图

图5中代码将创建好的adapterprotocolHandler进行了关联,在前文中分析过,默认情况protocolHandler就是Http11Protocol的实例,在Tomcat架构中各个组件及组件间关系(二)中分析过,该实例是在Digester解析ConnectorCreateRule时创建Connector对象的同时创建的

Tomcat的生命周期(二)_第7张图片
图7.Http11Protocol的构造器

Http11Protocol代表了对HTTP1.1协议进行处理的类,初始化时又创建了 JIoEndpointHttp11ConnectionHandler的实例,前者用于处理端到端的socket io请求,根据I/O方式的不同又可分为 AprEndpointJIoEndpointNioEndpoint;后者主要用于创建对应协议请求的处理器。 ((JIoEndpoint) endpoint).setHandler(cHandler)建立了两者之间的关系,最后三行代码分别设置了关闭 Socket延迟开关、 Socket连接超时时间和开启 tcpNoDelay选项
回到图5对协议处理类 protocolHandler进行初始化,底层调用了所有协议处理类的父类 AbstractHandlerinit()
Tomcat的生命周期(二)_第8张图片
图8. AbstractProtocol的init方法

对于Http的bio请求方式来说,这里 endpointNamehttp-bio-8080,协议处理类的初始化主要对相应的 endpoint进行初始化
Tomcat的生命周期(二)_第9张图片
图9. AbstractEndpoint的init()

又来一个模板方法,在父类 AbstractEndpoint中抽象了 bind(),交由不同类型的端到端类进行实现,本文中必然就是对应 JIoEndpointbind(),对应 代码清单1

    @Override
    public void bind() throws Exception {

        // Initialize thread count defaults for acceptor
        //      (1)
        if (acceptorThreadCount == 0) {
            acceptorThreadCount = 1;
        }
        // Initialize maxConnections
        //      (2)
        if (getMaxConnections() == 0) {
            // User hasn't set a value - use the default
            setMaxConnections(getMaxThreadsInternal());
        }
        //      (3)
        if (serverSocketFactory == null) {
            if (isSSLEnabled()) {
                serverSocketFactory =
                    handler.getSslImplementation().getServerSocketFactory(this);
            } else {
                serverSocketFactory = new DefaultServerSocketFactory(this);
            }
        }
        //      (4)
        if (serverSocket == null) {
            try {
                if (getAddress() == null) {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog());
                } else {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog(), getAddress());
                }
            } catch (BindException orig) {
                String msg;
                if (getAddress() == null)
                    msg = orig.getMessage() + " :" + getPort();
                else
                    msg = orig.getMessage() + " " +
                            getAddress().toString() + ":" + getPort();
                BindException be = new BindException(msg);
                be.initCause(orig);
                throw be;
            }
        }

    }

标注1处涉及一个成员变量acceptorThreadCount,该变量在AbstractEndpoint中,表示等待Socket连接的Acceptor线程个数。Acceptor是一个在AbstractEndpoint中定义的抽象内部类,该类实现了Runnable接口

Tomcat的生命周期(二)_第10张图片
图10. AbstractEndpoint中的抽象静态内部类Acceptor

该类中仅仅定义了 Acceptor的几种状态,并没有实现 run(),那必然就是在其子类中实现了,为了流程分析的整体性,我们暂且跳过 JIoEndpointAcceptor的具体实现,因为只有在启动时,该线程才会执行,待讲解到启动流程再做分析,我们接着代码清单1往下看
标注2处设置了每个 Endpoint允许的最大连接数,需要注意的是,这里不要和 Acceptor线程的连接数 acceptorThreadCount混淆,当 getMaxConnections()返回0时,socket允许最大连接数就由 getMaxThreadsInternal()指定
Tomcat的生命周期(二)_第11张图片
图11. AbstractEndpoint类中getMaxThreadsInternal()

在初始化 JIoEndpoint时,已经通过 setMaxConnections(int maxCon)将父类 maxConnections置为0,那么最大连接数即为 AbstractEndpoint类中的成员变量 maxThreads = 200
标注3创建了 ServerSocket工厂,根据是否在 server.xml中开启SSL安全协议 serverSocketFactory共有两种创建方式,默认不开启,对应 DefaultServerSocket。标注4根据这里的 serverSocketFactory创建对应的 serverSocket,到这里大家肯定都非常熟悉了,服务端套接字嘛。至此,所有容器和连接器的初始化工作结束,下面我们来看容器的启动过程
在 Tomcat的生命周期一文中,我们分析到 StandardServer的启动, StandardServer启动会调动子容器 StandardServicestartInternal()
Tomcat的生命周期(二)_第12张图片
图12. StandardService的startInternal

同的初始化流程一样, StandardService的启动流程也分为三部分,我们重点依然是 Container容器和 ConnectorContainer.start()对应 StandardEngine.startInternal()
Tomcat的生命周期(二)_第13张图片
图13. StandardEngine的startInternal

StandardServerStandardService启动不同的是 Container的子容器除了 StandardContext外都会调用公共父类 ContainerBasestartInternal
Tomcat的生命周期(二)_第14张图片
图14. ContainerBase的startInternal

findChildren()拿到成员变量HashMap children的所有value,该集合是在Digester解析容器对应规则的时候通过addChild(Container)放入值的,比如对于这里的StandardEngine来说,StandardHost是他的childrenStartChild实现了Callable接口,其call()调用了child.start(),对应StandardHoststartInternal()

Tomcat的生命周期(二)_第15张图片
图15. StandardHost的startInternal

方法中主要做了两件事:1. 给 StandardHostStandardPipeline又添加了一个阀门 ErrorReportValve;2.继续调用 ContainerBasestartInternal。但需要注意的是对于 StandardHost来说 children肯定是 StandardContext,此时通过 findChildren()得到的 StandardContext实际上是通过解析 server.xml中的 转变而来,该 StandardContext和通常意义上的 webapps/xxx.war并没有对应关系,况且很多时候我们并不会在 server.xml中添加 标签,而对于真正的 StandardContext的解析并不是通过 StandardHost.findChildren()得到。这时候我们需要看图14的第二个红框处, setState(LifecycleState.STARTING)StandardHost的生命周期状态设为 STARTING(注意此时流程已经进入 StandardHost,虽然图14对应的是 StandardEngine,但是两者都会调用 ContainerBase.startInternal(),代码是一样的,下面的说明也需要注意这一点),并发送对应的事件 START_EVENT给对应的监听器 HostConfig
Tomcat的生命周期(二)_第16张图片
图16. HostConfig的start()

重点在于最后一个判断,默认情况下 host.getDeployOnStartup()返回成员变量 deployOnStartup为true,表示一启动就加载web应用
Tomcat的生命周期(二)_第17张图片
图17. deployApps()

共有web应用部署方式:1.XML描述符;2.WAR;3.扩展文件夹,我们选择WAR方式进行分析。 appBase()得到 ${catalina.base}/webapps下对应的文件, filterAppPaths(appBase.list())得到 /webapps下所有的war文件,并滤除一些排除项, 代码清单2 展示了 deployWARs的具体逻辑

protected void deployWARs(File appBase, String[] files) {

    if (files == null)
        return;

    ExecutorService es = host.getStartStopExecutor();
    List> results = new ArrayList>();

    for (int i = 0; i < files.length; i++) {

        if (files[i].equalsIgnoreCase("META-INF"))
            continue;
        if (files[i].equalsIgnoreCase("WEB-INF"))
            continue;
        File war = new File(appBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
                war.isFile() && !invalidWars.contains(files[i]) ) {

            ContextName cn = new ContextName(files[i], true);

            if (isServiced(cn.getName())) {
                continue;
            }
            if (deploymentExists(cn.getName())) {
                DeployedApplication app = deployed.get(cn.getName());
                boolean unpackWAR = unpackWARs;
                if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) {
                    unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR();
                }
                if (!unpackWAR && app != null) {
                    // Need to check for a directory that should not be
                    // there
                    File dir = new File(appBase, cn.getBaseName());
                    if (dir.exists()) {
                        if (!app.loggedDirWarning) {
                            log.warn(sm.getString(
                                    "hostConfig.deployWar.hiddenDir",
                                    dir.getAbsoluteFile(),
                                    war.getAbsoluteFile()));
                            app.loggedDirWarning = true;
                        }
                    } else {
                        app.loggedDirWarning = false;
                    }
                }
                continue;
            }

            // Check for WARs with /../ /./ or similar sequences in the name
            if (!validateContextPath(appBase, cn.getBaseName())) {
                log.error(sm.getString(
                        "hostConfig.illegalWarName", files[i]));
                invalidWars.add(files[i]);
                continue;
            }

            results.add(es.submit(new DeployWar(this, cn, war)));
        }
    }

    for (Future result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString(
                    "hostConfig.deployWar.threaded.error"), e);
        }
    }
}

遍历每一个war包,对文件名称进行校验和特殊字符的处理,判断文件名对应的war包是否已经运行是否已经部署成功,最后将文件及其对应信息包装成DeployWar放入线程池中进行部署,由于DeployWar实现了Runable,所以这里的es.submit使用的是ExectuorService.submit(Runnable)这个重载方法,得到的Future中并没有返回值,下面result.get()的目的只是为了阻塞让所有的DeployWar任务执行完毕,我们来看DeployWar做了什么

Tomcat的生命周期(二)_第18张图片
图18. DeployWar

DeployWar中只是将当前需要加载的ContextName和对应的war文件传递给 deployWAR(String, File),如 代码清单3

    protected void deployWAR(ContextName cn, File war) {

        // Checking for a nested /META-INF/context.xml
        JarFile jar = null;
        InputStream istream = null;
        FileOutputStream fos = null;
        BufferedOutputStream ostream = null;

        File xml = new File(appBase(),
                cn.getBaseName() + "/META-INF/context.xml");

        boolean xmlInWar = false;
        try {
            jar = new JarFile(war);
            JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
            if (entry != null) {
                xmlInWar = true;
            }
        } catch (IOException e) {
            /* Ignore */
        } finally {
            if (jar != null) {
                try {
                    jar.close();
                } catch (IOException ioe) {
                    // Ignore;
                }
                jar = null;
            }
        }

        //
        Context context = null;
        try {
            if (deployXML && xml.exists() && unpackWARs && !copyXML) {
                synchronized (digesterLock) {
                    try {
                        context = (Context) digester.parse(xml);
                    } catch (Exception e) {
                        log.error(sm.getString(
                                "hostConfig.deployDescriptor.error",
                                war.getAbsolutePath()), e);
                    } finally {
                        digester.reset();
                        if (context == null) {
                            context = new FailedContext();
                        }
                    }
                }
                context.setConfigFile(xml.toURI().toURL());
            } else if (deployXML && xmlInWar) {
                synchronized (digesterLock) {
                    try {
                        jar = new JarFile(war);
                        JarEntry entry =
                            jar.getJarEntry(Constants.ApplicationContextXml);
                        istream = jar.getInputStream(entry);
                        context = (Context) digester.parse(istream);
                    } catch (Exception e) {
                        log.error(sm.getString(
                                "hostConfig.deployDescriptor.error",
                                war.getAbsolutePath()), e);
                    } finally {
                        digester.reset();
                        if (istream != null) {
                            try {
                                istream.close();
                            } catch (IOException e) {
                                /* Ignore */
                            }
                            istream = null;
                        }
                        if (jar != null) {
                            try {
                                jar.close();
                            } catch (IOException e) {
                                /* Ignore */
                            }
                            jar = null;
                        }
                        if (context == null) {
                            context = new FailedContext();
                        }
                        context.setConfigFile(
                                UriUtil.buildJarUrl(war, Constants.ApplicationContextXml));
                    }
                }
            } else if (!deployXML && xmlInWar) {
                // Block deployment as META-INF/context.xml may contain security
                // configuration necessary for a secure deployment.
                log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                        cn.getPath(), Constants.ApplicationContextXml,
                        new File(configBase(), cn.getBaseName() + ".xml")));
            } else {
                context = (Context) Class.forName(contextClass).newInstance();
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("hostConfig.deployWar.error",
                    war.getAbsolutePath()), t);
        } finally {
            if (context == null) {
                context = new FailedContext();
            }
        }

        boolean copyThisXml = false;
        if (deployXML) {
            if (host instanceof StandardHost) {
                copyThisXml = ((StandardHost) host).isCopyXML();
            }

            // If Host is using default value Context can override it.
            if (!copyThisXml && context instanceof StandardContext) {
                copyThisXml = ((StandardContext) context).getCopyXML();
            }

            if (xmlInWar && copyThisXml) {
                // Change location of XML file to config base
                xml = new File(configBase(), cn.getBaseName() + ".xml");
                try {
                    jar = new JarFile(war);
                    JarEntry entry =
                        jar.getJarEntry(Constants.ApplicationContextXml);
                    istream = jar.getInputStream(entry);

                    fos = new FileOutputStream(xml);
                    ostream = new BufferedOutputStream(fos, 1024);
                    byte buffer[] = new byte[1024];
                    while (true) {
                        int n = istream.read(buffer);
                        if (n < 0) {
                            break;
                        }
                        ostream.write(buffer, 0, n);
                    }
                    ostream.flush();
                } catch (IOException e) {
                    /* Ignore */
                } finally {
                    if (ostream != null) {
                        try {
                            ostream.close();
                        } catch (IOException ioe) {
                            // Ignore
                        }
                        ostream = null;
                    }
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException ioe) {
                            // Ignore
                        }
                        fos = null;
                    }
                    if (istream != null) {
                        try {
                            istream.close();
                        } catch (IOException ioe) {
                            // Ignore
                        }
                        istream = null;
                    }
                    if (jar != null) {
                        try {
                            jar.close();
                        } catch (IOException ioe) {
                            // Ignore;
                        }
                        jar = null;
                    }
                }
            }
        }

        DeployedApplication deployedApp = new DeployedApplication(cn.getName(),
                xml.exists() && deployXML && copyThisXml);

        long startTime = 0;
        // Deploy the application in this WAR file
        if(log.isInfoEnabled()) {
            startTime = System.currentTimeMillis();
            log.info(sm.getString("hostConfig.deployWar",
                    war.getAbsolutePath()));
        }

        try {
            // Populate redeploy resources with the WAR file
            deployedApp.redeployResources.put
                (war.getAbsolutePath(), Long.valueOf(war.lastModified()));

            if (deployXML && xml.exists() && copyThisXml) {
                deployedApp.redeployResources.put(xml.getAbsolutePath(),
                        Long.valueOf(xml.lastModified()));
            } else {
                // In case an XML file is added to the config base later
                deployedApp.redeployResources.put(
                        (new File(configBase(),
                                cn.getBaseName() + ".xml")).getAbsolutePath(),
                        Long.valueOf(0));
            }
            //        (1)
            Class clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener =
                (LifecycleListener) clazz.newInstance();
            context.addLifecycleListener(listener);

            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName() + ".war");

            host.addChild(context);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("hostConfig.deployWar.error",
                    war.getAbsolutePath()), t);
        } finally {
            // If we're unpacking WARs, the docBase will be mutated after
            // starting the context
            boolean unpackWAR = unpackWARs;
            if (unpackWAR && context instanceof StandardContext) {
                unpackWAR = ((StandardContext) context).getUnpackWAR();
            }
            if (unpackWAR && context.getDocBase() != null) {
                File docBase = new File(appBase(), cn.getBaseName());
                deployedApp.redeployResources.put(docBase.getAbsolutePath(),
                        Long.valueOf(docBase.lastModified()));
                addWatchedResources(deployedApp, docBase.getAbsolutePath(),
                        context);
                if (deployXML && !copyThisXml && (xmlInWar || xml.exists())) {
                    deployedApp.redeployResources.put(xml.getAbsolutePath(),
                            Long.valueOf(xml.lastModified()));
                }
            } else {
                // Passing null for docBase means that no resources will be
                // watched. This will be logged at debug level.
                addWatchedResources(deployedApp, null, context);
            }
            // Add the global redeploy resources (which are never deleted) at
            // the end so they don't interfere with the deletion process
            addGlobalRedeployResources(deployedApp);
        }

        deployed.put(cn.getName(), deployedApp);

        if (log.isInfoEnabled()) {
            log.info(sm.getString("hostConfig.deployWar.finished",
                war.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
        }
    }

代码逻辑比较长,我们挑重点的讲。标注1为每一个StandardContext创建一个监听器ContextConfig,该监听器的值是写死的,并通过host.getConfigClass()获得,之后为StandardContext设置名称、路劲、版本等信息,最后调用host.addChild(Container)建立StandardHost与所有StandardContext的关联。流程又走入生命周期的一个大循环内,调用LifecycleBase.start(),由于此时StandardContext刚刚创建出来,其生命周期状态为NEW,并不会进入启动流程而是先进行init()

Tomcat的生命周期(二)_第19张图片
图19. 当前对象为StandardContext流程debug截图

由于在 StandardHost.initInternal()没做什么关键操作,这里就不做分析了,之后在 LifecycleBase中会向 ContextConfig发送 AFTER_INIT_EVENT事件,此时 ContextConfig会对该事件做出响应,调用 init()进行 web.xml文件的解析规则设置,具体的分析过程已经在 Tomcat架构中各个组件及组件间关系(二)讲过
图14中的最后一句 threadStart()用于启动 ContainerBase中的 ContainerBackgroundProcessor线程,同样在 Tomcat架构中各个组件及组件间关系(二)提过,该线程的启动有一个先决条件,就是 backgroundProcessorDelay > 0,而 Container子容器中只有 StandardEngine对该值进行了覆盖,满足大于0的条件,因此可以说该线程启动入口只在 StandardEngine启动时调用父类的 startInternal()中。线程中会调用 processChildren()
Tomcat的生命周期(二)_第20张图片
图20. ContainerBackgroundProcessor中processChildren()

从图中可以看出虽然线程的启动入口只在 StandardEngine启动时,但代码采用了递归开启的方式,使得 StandardEngine下所有的子容器都能执行 backgroundProcess()的具体实现,如 代码清单4

    @Override
    public void backgroundProcess() {
        
        if (!getState().isAvailable())
            return;

        if (cluster != null) {
            try {
                cluster.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);                
            }
        }
        if (loader != null) {
            try {
                loader.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                
            }
        }
        if (manager != null) {
            try {
                manager.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);                
            }
        }
        Realm realm = getRealmInternal();
        if (realm != null) {
            try {
                realm.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);                
            }
        }
        Valve current = pipeline.getFirst();
        while (current != null) {
            try {
                current.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);                
            }
            current = current.getNext();
        }
        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
    }

clusterrealm调用backgroundProcess()非本文重点,这里不做分析。我们来看看loader.backgroundProcess(),在违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制中曾今阐述过war包加载需要依赖WebappLoader,而StandardContext又是个war一一对应的,那很明显这里就是调用WebappLoaderbackgroundProcess()

Tomcat的生命周期(二)_第21张图片
图21. WebappLoader的backgroundProcess

reloadable的一个属性,用于标明是否运行war在文件改动后自动重新加载, modified()用于检测war中是否有class或者resource文件存在改动,如果两者都为true则会调用 StandardContext.reload(),该方法的逻辑其实也非常简单,就是先pause容器,在stop容器,最后start。我们回到 代码清单4中最后一行,代码向 Container的所有子容器发送了 PERIODIC_EVENT,其中 HostConfig会对该事件做出响应,调用该类的 check()
Tomcat的生命周期(二)_第22张图片
图22. HostConfig的check()

图中的代码其实很大一部分就是上面说如果发布war包流程的重复,if中判断 autoDeploy自动部署属性是否打开,再从已部署应用的 Map deployed集合中得到所有war,进行部署前的校验,最后再次调用 deployApps()对三种形式的文件进行发布
至此, Container下所有容器的初始化和启动流程基本分析完毕, Connector分析了初始化流程,还顺带解释了Tomcat自动加载的原理,由于篇幅不宜过长,因此将 Connector的启动过程放在下一篇文章中再做分析

你可能感兴趣的:(Tomcat的生命周期(二))