springboot啟動流程 (1) 流程概覽

来源:https://www.cnblogs.com/xugf/archive/2023/06/15/17481884.html
-Advertisement-
Play Games

本文將通過閱讀源碼方式分析SpringBoot應用的啟動流程,不涉及Spring啟動部分(有相應的文章介紹)。 本文不會對各個流程做展開分析,後續會有文章介紹詳細流程。 # SpringApplication類 ## 應用啟動入口 使用以下方式啟動一個SpringBoot應用: ```java @S ...


本文將通過閱讀源碼方式分析SpringBoot應用的啟動流程,不涉及Spring啟動部分(有相應的文章介紹)。

本文不會對各個流程做展開分析,後續會有文章介紹詳細流程。

SpringApplication類

應用啟動入口

使用以下方式啟動一個SpringBoot應用:

@SpringBootApplication
public class SpringBootDemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootDemoApplication.class, args);
  }
}

run方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

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 {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 獲取應用env
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		// 列印banner
		Banner printedBanner = printBanner(environment);
		// 創建ApplicationContext
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		// 一些準備工作
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// refresh ApplicationContext
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// 調用listener
		listeners.started(context);
		// 調用ApplicationRunner和CommandLineRunner
		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;
}

獲取應用env

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 創建StandardServletEnvironment, 會初始化四個PropertySource:
	// servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment
	// 比如-Dserver.port=8888會在systemProperties中
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 添加defaultProperties和命令行配置參數即CommandLinePropertySource
	// 通常都沒有這兩個配置
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 使用ConfigurationPropertySourcesPropertySource封裝並暴露所有的PropertySource集
	ConfigurationPropertySources.attach(environment);
	// 添加ApplicationEnvironmentPreparedEvent事件並觸發multicastEvent載入應用配置文件
	listeners.environmentPrepared(environment);
	// 將spring.main.xx配置載入到SpringApplication對象
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

載入配置文件的入口在ConfigFileApplicationListener類中:

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	addPropertySources(environment, application.getResourceLoader());
}

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// 添加RandomValuePropertySource
	RandomValuePropertySource.addToEnvironment(environment);
	// 載入配置文件
	new Loader(environment, resourceLoader).load();
}

載入配置文件的源碼較多,此處不做記錄,簡單梳理一下流程:

  1. 載入active profile配置文件
    • 如果配置了spring.config.additional-location或spring.config.location參數,會使用它們作為配置文件。如果這兩個參數值是目錄,則會從這兩個目錄下查找配置文件
    • 預設從classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/目錄下查找application-xx.properties或application-xx.yml文件
    • 使用PropertiesPropertySourceLoader和YamlPropertySourceLoader解析配置文件
    • 會將配置參數封裝成OriginTrackedMapPropertySource類型對象,使用applicationConfig: [classpath:/application-dev.yml]之類的字元串作為PropertySource的名稱
  2. 載入預設的application.properties或application.yml文件
  3. 解析出來的所有PropertySource都會添加到environment的propertySources中,propertySources是一個MutablePropertySources對象,管理著所有的PropertySource集,在這個過程中,添加的先後順序決定了配置的優先順序

創建ApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				// AnnotationConfigServletWebServerApplicationContext類
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				// AnnotationConfigReactiveWebServerApplicationContext類
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				// AnnotationConfigApplicationContext類
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
			}
		} catch (ClassNotFoundException ex) {
			throw new IllegalStateException("Unable create a default ApplicationContext", ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

prepareContext

private void prepareContext(
		ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments,
		Banner printedBanner) {
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	// Apply any ApplicationContextInitializers to the context before it is refreshed.
	// ApplicationContextInitializers集是在創建SpringApplication對象的時候初始化的
	applyInitializers(context);
	// 觸發contextPrepared事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 獲取BeanFactory並註冊必要的Bean
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	// 註冊啟動參數
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	// 註冊banner printer
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	// 設置是否允許Bean覆蓋,使用spring.main.allowBeanDefinitionOverriding參數配置
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	// 將SpringApplication.run(Xxx.class, args)方法傳入的Class註冊到容器
	// 使用AnnotatedBeanDefinitionReader.register(Class<?>...)方法註冊啟動類
	load(context, sources.toArray(new Object[0]));
	// 觸發contextLoaded事件
	listeners.contextLoaded(context);
}

refreshApplicationContext

protected void refresh(ConfigurableApplicationContext applicationContext) {
	applicationContext.refresh();
}

調用的是ServletWebServerApplicationContext的refresh方法:

public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	} catch (RuntimeException ex) {
		// 關閉web server
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.stop();
		}
		throw ex;
	}
}

絕大多數的refresh邏輯都在AbstractApplicationContext類裡面,ServletWebServerApplicationContext中會在onRefresh階段創建webServer:

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

調用ApplicationRunner和CommandLineRunner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

SpringBootApplication註解

指示一個配置類,該類聲明一個或多個@Bean方法,並觸發自動配置和組件掃描。這是一個方便的註解,相當於聲明@Configuration、@EnableAutoConfiguration和@ComponentScan註解。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use scanBasePackageClasses
	 * for a type-safe alternative to String-based package names.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to scanBasePackages for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * The BeanNameGenerator class to be used for naming detected components
	 * within the Spring container.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	 * Specify whether @Bean methods should get proxied in order to enforce
	 * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
	 * case of direct @Bean method calls in user code. This feature requires
	 * method interception, implemented through a runtime-generated CGLIB subclass which
	 * comes with limitations such as the configuration class and its methods not being
	 * allowed to declare final.
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

SpringBootConfiguration註解

指示一個類提供Spring Boot application @Configuration功能。可以替代Spring的標準@Configuration註解,以便可以自動找到配置類。

應用程式應該只標註一個@SpringBootConfiguration,大多數SpringBoot應用程式將從@SpringBootApplication繼承它。

@Configuration
public @interface SpringBootConfiguration {

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

EnableAutoConfiguration註解

啟用SpringBoot自動裝配功能,嘗試猜測和配置可能需要的組件Bean。

自動裝配類通常是根據類路徑和定義的Bean來應用的。例如,如果類路徑上有tomcat-embedded.jar,那麼可能需要一個TomcatServletWebServerFactory(除非已經定義了自己的Servlet WebServerFactory Bean)。

自動裝配試圖儘可能地智能化,並將隨著開發者定義自己的配置而取消自動裝配相衝突的配置。開發者可以使用exclude()排除不想使用的配置,也可以通過spring.autoconfig.exclude屬性排除這些配置。自動裝配總是在用戶定義的Bean註冊之後應用。

用@EnableAutoConfiguration註解標註的類所在包具有特定的意義,通常用作預設掃描的包。通常建議將@EnableAutoConfiguration(如果沒有使用@SpringBootApplication註解)放在根包中,以便可以搜索所有子包和類。

自動裝配類是普通的Spring @Configuration類,使用SpringFactoriesLoader機制定位。通常使用@Conditional方式裝配,最常用的是@ConditionalOnClass和@ConditionalOnMissingBean註解。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * 當類路徑下沒有指定的類時,可以使用這個屬性指定排除的類
	 */
	String[] excludeName() default {};
}

AutoConfigurationPackage註解

Registers packages with AutoConfigurationPackages. When no base packages or base package classes are specified, the package of the annotated class is registered.

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};
}

AutoConfigurationImportSelector類

DeferredImportSelector介面的實現類,處理自動裝配,導出所有需要自動裝配的類。

創建WebServer

SpringBoot會在onRefresh階段創建webServer,首先從spring容器獲取ServletWebServerFactory,然後調用getWebServer方法創建webServer。

getWebServer方法需要傳入ServletContextInitializer集來初始化ServletContext。

@FunctionalInterface
public interface ServletContextInitializer {

	void onStartup(ServletContext servletContext) throws ServletException;
}

我們開發者如果需要使用ServletContextInitializer來初始化ServletContext的話,也可以編寫一個實現類,然後將其註冊到spring容器即可。

另外,SpringBoot還會自動裝配DispatcherServletAutoConfiguration類,這個類會創建DispatcherServlet和DispatcherServletRegistrationBean。DispatcherServlet是SpringWebMvc的最核心組件,DispatcherServletRegistrationBean實現了ServletContextInitializer介面,可以將DispatcherServlet註冊到ServletContext。以TomcatServletWebServerFactory為例,這個類會通過TomcatStarter來調用所有的ServletContextInitializer,TomcatStarter實現了ServletContainerInitializer介面,Tomcat的ServletContext在啟動階段會調用ServletContainerInitializer的onStartup方法來初始化Servlet容器。

SpringBoot啟動流程

  • 初始化environment應用配置參數:servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment及配置文件等
  • 創建ApplicationContext對象,SpringBoot應用預設使用的是AnnotationConfigServletWebServerApplicationContext類
  • prepareContext階段:觸發一些事件,將啟動類註冊到Spring容器
  • refresh階段:掃描應用組件,自動裝配
  • onRefresh階段:創建並初始化WebServer
  • 調用ApplicationRunner和CommandLineRunner

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • > 本文首發於公眾號:Hunter後端 > 原文鏈接:[celery筆記四之在Django中使用celery](https://mp.weixin.qq.com/s/O-vHxvDbU6aSGM63GwLLdw) 這一篇筆記介紹一下如何在 Django 系統中使用 celery。 如果是想純粹使用 ...
  • # 網格線分級 一般情況下,我們設置網格線都不會太在意密度和精度的問題,導致下圖這種看的有些眼花,橫坐標在比較密集的時候很容易看竄行。 ![image-20230615191231845](https://img2023.cnblogs.com/blog/2862884/202306/2862884 ...
  • 分析 Switch 相較於 if 的優點 1、switch 執行效率 高於 if 的執行效率 分析: switch是在編譯階段將子函數的地址和判斷條件綁定了,只要直接將a的直接映射到子函數地址去執行就可以了, if處理起來首先要把a的值放到CPU的寄存器中,然後要把比較的值放到CPU的另一個寄存器中 ...
  • ## 概述 RMI 是 Java 提供的一個完善的簡單易用的遠程方法調用框架,採用客戶/伺服器通信方式,在伺服器上部署了提供各種服務的遠程對象,客戶端請求訪問伺服器上遠程對象的方法,它要求客戶端與伺服器端都是 Java 程式 RMI 框架採用代理來負責客戶與遠程對象之間通過 Socket 進行通信的 ...
  • 基於java的智能停車場管理系統設計與實現,可適用於java車輛管理,java停車場信息管理平臺,小區停車管理平臺,小區停車,物業停車管理,智慧停車場管理系統,智慧小區停車場平臺,車輛AI識別,車輛識別。 ...
  • 在調用超類型構造函數之前無法引用“XxxClass.xxx” -----在一個類的構造器方法還未執行的時候,我們無法使用這個類的成員屬性或成員方法。 ...
  • 基於java的校園教務系統設計與實現,可適用於學生教務系統,教務管理系統,學生管理系統,學生選課系統,學生評教,評價教師系統,教務系統設計,java教務管理系統,學生成績管理系統,學生信息管理系統。 ...
  • 在某些時候我們的系統中會出現一些無法被正常刪除的文件,如果想要強制刪除則需要在驅動層面對其進行解鎖後才可刪掉,而所謂的解鎖其實就是釋放掉文件描述符(句柄表)占用,文件解鎖的核心原理是通過調用`ObSetHandleAttributes`函數將特定句柄設置為可關閉狀態,然後在調用`ZwClose`將其... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...