Spring Boot启动过程(六):内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动

本文详细剖析了Spring Boot应用中内嵌Tomcat的启动过程,包括StandardEngine、StandardHost、StandardContext和StandardWrapper的启动细节。从线程启动、容器初始化、错误报告阀的注册、资源加载、类加载器创建、 Realm处理、扩展验证、命名服务支持到Servlet的启动等步骤,揭示了Spring Boot与Tomcat协同工作的底层机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 看代码有助于线上出现预料之外的事的时候,不至于心慌。。。 

  StandardEngine[Tomcat].StandardHost[localhost]的启动与StandardEngine不在同一个线程中,它的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;
            }

        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStartFailed"));
        }
复制代码

 

复制代码
    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; } }
复制代码

  这个start流程中,initInternal方法是ContainerBase的代码,还是那个初始化startStopExecutor的,线程名例如Thread[localhost-startStop-1,5,main],这次是用来初始化host的子容器的,然后是StandardHost中的startInternal方法,主要是注册了一个errorValue,如果现有的pipeline中没有errorvalue,则反射创建org.apache.catalina.valves.ErrorReportValve实例,并加入pipeline中,容器pipeline加入Value时会发布一个Container.ADD_VALVE_EVENT事件,与engine一样,之后进入ContainerBase的startInternal,但是这次Realm是null不需要启动,然后findChildren出StandardEngine[Tomcat]. StandardHost [localhost].StandardContext[],然后同样新开个线程new StartChild,start同样是上面的代码,需要特别说明的是,这次before_init的事件有监听的了,FixContextListener,DisablePersistSessionListener,MemoryLeakTrackingListener;FixContextListener监听的处理,会加入一个用于不做用户身份认证的安全检查的Value:

复制代码
                Context context = (Context) event.getLifecycle();
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                    context.setConfigured(true);
                }
                // LoginConfig is required to process @ServletSecurity
                // annotations
                if (context.getLoginConfig() == null) {
                    context.setLoginConfig(
                            new LoginConfig("NONE", null, null, null));
                    context.getPipeline().addValve(new NonLoginAuthenticator()); }
复制代码

   DisablePersistSessionListener监听只处理start事件,所以这里只判断了一下发现不是就出去了,其实这里可以思考下,有没有更好的办法,让监听不只是广播方式,能不能用订阅方式,先不细想了,接着看代码,MemoryLeakTrackingListener只监听了after_start事件,这步同样什么都没做。

  于是来到了StandardContext的initInternal,它的super.initInternal又是一个startStopExecutor,ContainerBase的super.initInternal就不再说了,发送j2ee.object.created消息:

            Notification notification = new Notification("j2ee.object.created",
                    this.getObjectName(), sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);

   Notification是EventObject的子类,代表由MBean发出的通知,MBean server发出的通知会包含发出的MBean的引用,如果MBean注册了监听,可以通过object name或引用获取消息发出者,官方建议使用object name;sendNotification方法:

复制代码
    /**
     * Sends a notification.
     *
     * If an {@code Executor} was specified in the constructor, it will be given one
     * task per selected listener to deliver the notification to that listener.
     *
     * @param notification The notification to send.
     */
    public void sendNotification(Notification notification) {

        if (notification == null) {
            return;
        }

        boolean enabled;

        for (ListenerInfo li : listenerList) {
            try { enabled = li.filter == null || li.filter.isNotificationEnabled(notification); } catch (Exception e) { if (logger.debugOn()) { logger.debug("sendNotification", e); } continue; } if (enabled) { executor.execute(new SendNotifJob(notification, li)); } } }
复制代码

  发完消息就转变状态为初始化完成,因为监听器是注册在context容器上的,于是after_init事件又触发了那三个监听器,这一阶段监听器什么都没处理走了下过场而已;before_start同走过场;然后StandardContext的startInternal方法,发布了个j2ee.state.starting消息object name为Tomcat:j2eeType=WebModule,name=//localhost/,J2EEApplication=none, J2EEServer=none;setConfigured(false)还没有正确的配置;设置WebResourceRoot,WebResourceRoot提供整个应用资源处理类的各种方法,内嵌用的实现类是StandardRoot,set的过程中加了写锁:

复制代码
            try {
                setResources(new StandardRoot(this));
            } catch (IllegalArgumentException e) {
                log.error(sm.getString("standardContext.resourcesInit"), e);
                ok = false;
            }
复制代码

   StandardRoot的属性allResources:

复制代码
    private final List<List<WebResourceSet>> allResources =
            new ArrayList<>();
    {
        allResources.add(preResources);
        allResources.add(mainResources);
        allResources.add(classResources);
        allResources.add(jarResources);
        allResources.add(postResources);
    }
复制代码

  http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/WebResourceRoot.html有相关说明,我就不翻译了。

  set之后就是启动resourcesStart,initInternal执行的是StandardRoot的initInternal方法,super.initInternal中依然是那两行代码,register(cache, getObjectNameKeyProperties() + ",name=Cache")会发送MBeanServerNotification. REGISTRATION_NOTIFICATION通知,生成ObjectName这里cacheJmxName是Tomcat:type=WebResourceRoot,host=localhost,context=/,name=Cache;registerURLStreamHandlerFactory里面的代码是TomcatURLStreamHandlerFactory.register()这行代码的注释说这是为了支持war包内的jar资源的。之后是循环上面的allResources,init里面加入的webResourceSet,但是由于全都是空的,所以等于没执行,就不说了,回头再仔细看看什么情况下回不为空,还是内嵌的就是空的。createMainResourceSet主要是设置个主目录,例如/tmp/tomcat-docbase.3031819619941848514.80,然后是各种资源该放在哪个子目录的一些设置代码;这次资源有一个了,所以可以有一个start了,DirResourceSet的;super.initInternal()的super是AbstractFileResourceSet:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值