springboot內(nèi)嵌tomcat代碼走讀(一)

花了一個(gè)禮拜的時(shí)間赊窥,大致走讀了一下springboot內(nèi)嵌tomcat的代碼逝钥,為下一步自己實(shí)現(xiàn)一個(gè)web容器做知識(shí)儲(chǔ)備隘弊。這里對(duì)走讀的代碼做一個(gè)大致的記錄乞而。

springboot內(nèi)嵌tomcat

我們知道送悔,springboot自動(dòng)裝配的相關(guān)功能封裝在spring-boot-autoconfigure包中。這里通過(guò)走讀spring-boot的2.4.0-M版本的代碼來(lái)大致看一下tomcat的啟動(dòng)過(guò)程爪模。
首先欠啤,寫(xiě)一個(gè)簡(jiǎn)單的demo,調(diào)用springboot的run方法屋灌,一步一步來(lái)看tomcat如何啟動(dòng)的洁段。

@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class);
    }
}

查看run方法里做了什么。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            //封裝參數(shù)
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //準(zhǔn)備springboot運(yùn)行相關(guān)的環(huán)境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            //確定spring的上下文信息共郭,這里是webservlet類型眉撵,通過(guò)反射得到AnnotationConfigServletWebServerApplicationContext
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class<?>[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //這里真正調(diào)到spring里,進(jìn)行bean的初始化
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

這里落塑,初始化的context是一個(gè)AnnotationConfigServletWebServerApplicationContext。我們看一下該類的繼承關(guān)系罐韩。

AnnotationConfigServletWebServerApplicationContext.png

本篇日記重點(diǎn)看的就是refreshContext(context)憾赁,這個(gè)真正的進(jìn)入spring bean的處理。也在這個(gè)方法里散吵,進(jìn)行了tomcat的初始化及啟動(dòng)龙考。繼續(xù)走讀這個(gè)方法的代碼。一步一步跟下去矾睦,就進(jìn)入了AbstractApplicationContextrefresh方法中。方法的代碼如下:

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

通過(guò)spring的注釋,可以大致猜測(cè)一膨,關(guān)于tomcat的初始化诞仓,應(yīng)該在方法onRefresh中實(shí)現(xiàn)。當(dāng)然赁温,這里是通過(guò)debug源碼坛怪,看調(diào)用棧信息能確切的知道這個(gè)方法就是tomcat初始化的入口淤齐。這個(gè)方法實(shí)現(xiàn)在子類里。上面在創(chuàng)建spring的context時(shí)袜匿,我們看到更啄,這里創(chuàng)建的是一個(gè)ServletWebServerApplicationContextonRefresh就在該類中實(shí)現(xiàn)居灯。進(jìn)入該方法祭务,看到如下代碼:

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

createWebServer就是創(chuàng)建webserver的代碼。代碼:

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

ServletWebServerFactory為自動(dòng)裝配進(jìn)來(lái)怪嫌,此時(shí)在BeanFactory中已經(jīng)存在了义锥。具體代碼配置在spring-boot-autoconfigure包的META-INF/spring.factories中。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
......
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

ServletWebServerFactoryAutoConfiguration中import了一些類喇勋,其中impot了EmbeddedTomcat缨该。

@Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {

        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(
                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers()
                    .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers()
                    .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers()
                    .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }

在這里生成了一個(gè)TomcatServletWebServerFactory的bean。
再次回到createWebServer中川背,this.webServer = factory.getWebServer(getSelfInitializer());從factory中生產(chǎn)出一個(gè)webserver贰拿。我們看這個(gè)webServer是如何生產(chǎn)出來(lái)的。

public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }

上面這段代碼熄云,就正式進(jìn)入到了tomcat的領(lǐng)域了膨更。為了能更好的閱讀代碼,我們需要了解tomcat的體系結(jié)構(gòu)缴允。這里通過(guò)代碼的閱讀和分析荚守,總結(jié)了一張結(jié)構(gòu)圖。

tomcat結(jié)構(gòu).png

tomcat的初始化练般,無(wú)非就是將圖中的各個(gè)部分初始化矗漾,塞到對(duì)應(yīng)的位置。有了這張圖薄料,代碼讀起來(lái)就比較輕松了敞贡,代碼也相對(duì)簡(jiǎn)單,可以自己按照?qǐng)D中的結(jié)構(gòu)摄职,進(jìn)行走讀誊役。這里不再記錄。
getWebServer代碼走完谷市,tomcat就初始化完成了蛔垢,那么是不是就可以接收請(qǐng)求了呢。當(dāng)然不行迫悠。socket還沒(méi)起來(lái)鹏漆,tomcat的工作線程還沒(méi)起來(lái)。這部分功能不在上面的onRefresh中,而在接下來(lái)的finishRefresh中甫男,只有所有的準(zhǔn)備工作都已經(jīng)完成了且改,請(qǐng)求才能接進(jìn)來(lái)。這部分功能將在下一篇日記中走讀板驳。
至此又跛,springboot的內(nèi)嵌tomcat初始化完成。這里只記錄了主流程的初始化若治,其中一些配置類信息如ServerProperties等的初始化并沒(méi)有在這篇日記中體現(xiàn)慨蓝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市端幼,隨后出現(xiàn)的幾起案子礼烈,更是在濱河造成了極大的恐慌,老刑警劉巖婆跑,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件此熬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡滑进,警方通過(guò)查閱死者的電腦和手機(jī)犀忱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扶关,“玉大人阴汇,你說(shuō)我怎么就攤上這事〗诨保” “怎么了搀庶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)铜异。 經(jīng)常有香客問(wèn)我哥倔,道長(zhǎng),這世上最難降的妖魔是什么揍庄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任未斑,我火速辦了婚禮,結(jié)果婚禮上币绩,老公的妹妹穿的比我還像新娘。我一直安慰自己府阀,他們只是感情好缆镣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著试浙,像睡著了一般董瞻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天钠糊,我揣著相機(jī)與錄音挟秤,去河邊找鬼。 笑死抄伍,一個(gè)胖子當(dāng)著我的面吹牛艘刚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播截珍,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼攀甚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了岗喉?” 一聲冷哼從身側(cè)響起秋度,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钱床,沒(méi)想到半個(gè)月后荚斯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡查牌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年事期,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧免。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刑赶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懂衩,到底是詐尸還是另有隱情撞叨,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布浊洞,位于F島的核電站牵敷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏法希。R本人自食惡果不足惜枷餐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苫亦。 院中可真熱鬧毛肋,春花似錦、人聲如沸屋剑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)唉匾。三九已至孕讳,卻和暖如春匠楚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厂财。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工芋簿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人璃饱。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓与斤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親帜平。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幽告,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354