Spring Boot從main方法到內(nèi)嵌Tomcat的全過程(自動化流程)
Spring Boot的啟動過程是一個精心設(shè)計的自動化流程,下面我將詳細(xì)闡述從main方法開始到內(nèi)嵌Tomcat啟動的全過程。
1. 入口:main方法
一切始于一個簡單的main方法:
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
2. SpringApplication初始化
SpringApplication.run()方法內(nèi)部會創(chuàng)建一個SpringApplication實例:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}
2.1 構(gòu)造階段
在SpringApplication構(gòu)造函數(shù)中完成以下關(guān)鍵操作:
- 推斷應(yīng)用類型:判斷是Servlet應(yīng)用(Spring MVC)還是Reactive應(yīng)用(Spring WebFlux)
 - 加載ApplicationContextInitializer:通過META-INF/spring.factories加載
 - 加載ApplicationListener:同樣通過spring.factories機(jī)制加載
 - 推斷主配置類:通過堆棧分析找到包含main方法的類
 
3. 運行階段:run()方法
run()方法是整個啟動過程的核心:
public ConfigurableApplicationContext run(String... args) {
    // 1. 創(chuàng)建并啟動計時器
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 2. 初始化應(yīng)用上下文和異常報告器
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 3. 獲取SpringApplicationRunListeners并啟動
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 4. 準(zhǔn)備環(huán)境
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 5. 打印Banner
        Banner printedBanner = printBanner(environment);
        // 6. 創(chuàng)建應(yīng)用上下文
        context = createApplicationContext();
        // 7. 準(zhǔn)備應(yīng)用上下文
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 8. 刷新應(yīng)用上下文(關(guān)鍵步驟)
        refreshContext(context);
        // 9. 刷新后處理
        afterRefresh(context, applicationArguments);
        // 10. 停止計時器并發(fā)布啟動完成事件
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        // 11. 執(zhí)行Runner
        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;
}4. 創(chuàng)建應(yīng)用上下文
createApplicationContext()方法根據(jù)應(yīng)用類型創(chuàng)建不同的應(yīng)用上下文:
- Servlet環(huán)境:創(chuàng)建
AnnotationConfigServletWebServerApplicationContext - Reactive環(huán)境:創(chuàng)建
AnnotationConfigReactiveWebServerApplicationContext - 普通環(huán)境:創(chuàng)建
AnnotationConfigApplicationContext 
對于Web應(yīng)用,會創(chuàng)建AnnotationConfigServletWebServerApplicationContext,它繼承自ServletWebServerApplicationContext。
5. 準(zhǔn)備應(yīng)用上下文
prepareContext()方法完成以下工作:
- 將環(huán)境綁定到上下文
 - 后置處理上下文
 - 應(yīng)用所有初始化器
 - 發(fā)布ContextPrepared事件
 - 注冊主配置類bean定義
 - 發(fā)布ContextLoaded事件
 
6. 刷新應(yīng)用上下文
refreshContext()最終調(diào)用AbstractApplicationContext.refresh(),這是Spring容器的核心刷新流程:
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 準(zhǔn)備刷新
        prepareRefresh();
        // 2. 獲取新的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 3. 準(zhǔn)備BeanFactory
        prepareBeanFactory(beanFactory);
        try {
            // 4. 后置處理BeanFactory
            postProcessBeanFactory(beanFactory);
            // 5. 調(diào)用BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            // 6. 注冊BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            // 7. 初始化MessageSource
            initMessageSource();
            // 8. 初始化事件廣播器
            initApplicationEventMulticaster();
            // 9. 初始化特殊bean(由子類實現(xiàn))
            onRefresh();
            // 10. 注冊監(jiān)聽器
            registerListeners();
            // 11. 初始化所有非懶加載單例
            finishBeanFactoryInitialization(beanFactory);
            // 12. 完成刷新
            finishRefresh();
        }
        catch (BeansException ex) {
            // 處理異常...
        }
    }
}7. 內(nèi)嵌Tomcat啟動的關(guān)鍵:onRefresh()
對于Servlet Web應(yīng)用,ServletWebServerApplicationContext重寫了onRefresh()方法:
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}createWebServer()是內(nèi)嵌服務(wù)器啟動的關(guān)鍵:
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        // 1. 獲取WebServer工廠(Tomcat, Jetty或Undertow)
        ServletWebServerFactory factory = getWebServerFactory();
        // 2. 創(chuàng)建WebServer
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}8. Tomcat服務(wù)器創(chuàng)建過程
以Tomcat為例,TomcatServletWebServerFactory.getWebServer()方法:
public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 1. 創(chuàng)建Tomcat實例
    Tomcat tomcat = new Tomcat();
    // 2. 配置基礎(chǔ)目錄
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 3. 配置連接器
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 4. 配置Host
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    // 5. 準(zhǔn)備上下文
    prepareContext(tomcat.getHost(), initializers);
    // 6. 創(chuàng)建TomcatWebServer并啟動
    return getTomcatWebServer(tomcat);
}9. 啟動Tomcat
在TomcatWebServer構(gòu)造函數(shù)中完成Tomcat的啟動:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    initialize();
}
private void initialize() throws WebServerException {
    // 啟動Tomcat
    this.tomcat.start();
    // 啟動一個守護(hù)線程來等待停止命令
    startDaemonAwaitThread();
}10. 自動配置的關(guān)鍵
整個過程中,自動配置是通過@SpringBootApplication注解中的@EnableAutoConfiguration實現(xiàn)的:
- 在
invokeBeanFactoryPostProcessors()階段會處理自動配置 AutoConfigurationImportSelector會加載META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的配置類- 對于Tomcat,會加載
ServletWebServerFactoryAutoConfiguration - 這個配置類通過
@Import引入了EmbeddedTomcat等配置 
總結(jié)流程
- 啟動main方法
 - 創(chuàng)建SpringApplication實例
 - 運行run()方法
 - 準(zhǔn)備環(huán)境
 - 創(chuàng)建應(yīng)用上下文(AnnotationConfigServletWebServerApplicationContext)
 - 準(zhǔn)備上下文(注冊配置類等)
 - 刷新上下文(核心)
- 調(diào)用onRefresh()
 - 創(chuàng)建內(nèi)嵌Web服務(wù)器(Tomcat)
 - 啟動Tomcat
 
 - 發(fā)布啟動完成事件
 - 執(zhí)行Runner
 
到此這篇關(guān)于Spring Boot從main方法到內(nèi)嵌Tomcat的全過程(自動化流程)的文章就介紹到這了,更多相關(guān)Spring Boot啟動過程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
 Java服務(wù)假死之生產(chǎn)事故的排查與優(yōu)化問題
在服務(wù)器上通過curl命令調(diào)用一個Java服務(wù)的查詢接口,半天沒有任何響應(yīng),怎么進(jìn)行這一現(xiàn)象排查呢,下面小編給大家記一次生產(chǎn)事故的排查與優(yōu)化——Java服務(wù)假死問題,感興趣的朋友一起看看吧2022-07-07
 如何解決java.net.BindException:地址已在使用問題
當(dāng)Zookeeper啟動報錯“java.net.BindException:地址已在使用”時,通常是因為指定的端口已被其他進(jìn)程占用,解決這個問題需要按照以下步驟操作:首先,使用命令如lsof -i:2181找到占用該端口的進(jìn)程號;其次,使用kill命令終止該進(jìn)程2024-09-09
 Java的Flowable工作流之加簽轉(zhuǎn)簽詳解
這篇文章主要介紹了Java的Flowable工作流之加簽轉(zhuǎn)簽詳解,Flowable是一個開源的工作流引擎,它提供了一套強(qiáng)大的工具和功能,用于設(shè)計、執(zhí)行和管理各種類型的工作流程,需要的朋友可以參考下2023-11-11
 IDEA下創(chuàng)建SpringBoot+MyBatis+MySql項目實現(xiàn)動態(tài)登錄與注冊功能
這篇文章主要介紹了IDEA下創(chuàng)建SpringBoot+MyBatis+MySql項目實現(xiàn)動態(tài)登錄與注冊功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
 spring/springboot整合dubbo詳細(xì)教程
今天教大家如何使用spring/springboot整合dubbo,文中有非常詳細(xì)的圖文介紹及代碼示例,對正在學(xué)習(xí)java的小伙伴有很好地幫助,需要的朋友可以參考下2021-05-05
 淺談常用Java數(shù)據(jù)庫連接池(小結(jié))
這篇文章主要介紹了淺談常用Java數(shù)據(jù)庫連接池(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

