【Spring boot】啟動過程源碼分析
- 2022 年 10 月 6 日
- 筆記
- Spring Boot, spring
啟動過程結論
- 推測web應用類型。
- spi的方式獲取BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationContextInitializer對象。
- 通過調用棧推測出main()方法所在的類。
- 調用啟動方法:run(String... args)。後面的步驟在這個方法內部!
- 觸發SpringApplicationRunListener的starting()。
- 創建Environment對象。
- 觸發SpringApplicationRunListener的environmentPrepared()。
- 列印Banner。
- 創建Spring容器對象(ApplicationContext)。
- 利用ApplicationContextInitializer初始化Spring容器對象。
- 觸發SpringApplicationRunListener的contextPrepared()。
- 調用DefaultBootstrapContext對象的close()。
- 將啟動類作為配置類註冊到Spring容器中(load()方法)。
- 觸發SpringApplicationRunListener的contextLoaded()。
- 刷新Spring容器。
- 觸發SpringApplicationRunListener的started()。
- 調用ApplicationRunner和CommandLineRunner。
- 觸發SpringApplicationRunListener的ready()。
- 上述過程拋異常了就觸發SpringApplicationRunListener的failed()。
入口位置
- 第一步構造對象:new SpringApplication(primarySources)
- 第二步調用SpringApplication.run(String... args)
// 我們自己寫的main方法
@SpringBootApplication
public class ZfcqApp {
public static void main(String[] args) {
SpringApplication.run(ZfcqApp.class, args);
}
}
/**
* 他會調用SpringApplication的run方法
* primarySource:我們傳入的類的class
* args:我們傳入的參數,一般是啟動的時候-D制定的參數
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
/**
* 繼續看內部調用的run方法
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 倆步,第一步構造對象:new SpringApplication(primarySources)
// 第二步調用SpringApplication.run(String... args)
// 這裡我們可以在main方法中分開倆步去寫,從而可以在中間設置SpringApplication對象的資訊
return new SpringApplication(primarySources).run(args);
}
構造SpringApplication源碼分析
/**
* 調用倆個參數的構造方法
* primarySources:我們主類的class
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* 倆個產生的構造方法
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 賦值到全局,這裡第一次傳入的是null
this.resourceLoader = resourceLoader;
// 主類存在的判斷
Assert.notNull(primarySources, "PrimarySources must not be null");
// 賦值到全局,這裡一般傳入的是我們的main方法的類
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推測WEB應用的類型(NONE、SERVLET、REACTIVE)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 從spring.factories中獲取BootstrapRegistryInitializer對象的值
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 從spring.factories中獲取ApplicationContextInitializer對象的值
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 從spring.factories中獲取ApplicationListener對象的值。非常重要的有一個是EnvironmentPostProcessorApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推測出main方法所在的類!SpringApplication.run(ZfcqApp.class, args);可以傳任意的類!
this.mainApplicationClass = deduceMainApplicationClass();
}
推測web應用類型
- REACTIVE:web應用。
- NONE:無Servlet,不是web應用。
- SERVLET:除去上面倆種的其他應用。
static WebApplicationType deduceFromClasspath() {
// 如果項目依賴中存在org.springframework.web.reactive.DispatcherHandler,並且不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.REACTIVE
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 如果項目依賴中不存在org.springframework.web.reactive.Dispatche#rHandler,也不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.NONE
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 否則,應用類型為WebApplicationType.SERVLET
return WebApplicationType.SERVLET;
}
推測出Main類(main()方法所在的類)
private Class<?> deduceMainApplicationClass() {
try {
// 獲取調用棧數組
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 找到調用棧中的main方法!
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
核心的啟動方法:run(String... args)
public ConfigurableApplicationContext run(String... args) {
// 開始時間
long startTime = System.nanoTime();
// 創建引導啟動器,類似一個ApplicationContext,可以往裡面添加一些對象。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// spring容器對象,開始的時候為空
ConfigurableApplicationContext context = null;
// 與awt有關,一般用不上
configureHeadlessProperty();
// spring boot的啟動監聽器
// 從spring.factories中獲取SpringApplicationRunListener對象的值
// 默認會拿到EventPublishingRunListener,他會啟動各個地方的ApplicationEvent事件。
SpringApplicationRunListeners listeners = getRunListeners(args);
// 發布開始啟動的事件:ApplicationstartingEvent
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 把run方法的參數進行封裝
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 準備environment對象:包括作業系統、jvm、ymal、properties....配置
// 發布一個ApplicationEnvironmentPreparedEvent事件。
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 默認spring.beaninfo.ignore為true,表示不需要jdk去快取BeanInfo資訊,spring自己快取。
// 這裡是spring在創建bean的時候會利用jdk的一些工具來解析一個類的相關資訊,jdk在解析一個類的資訊的時候會進行快取,這裡就是禁止了jdk的快取。
configureIgnoreBeanInfo(environment);
// 列印Banner
Banner printedBanner = printBanner(environment);
// 根據應用類型,創建spring容器
context = createApplicationContext();
// jdk9的一個機制,默認沒做任何操作
context.setApplicationStartup(this.applicationStartup);
// 準備容器的操作
// 利用ApplicationContextInitializer初始化spring容器
// 發布ApplicationContextInitializedEvent(容器初始化完成)事件
// 發布BootstrapContextClosedEvent(關閉引導容器)事件
// 註冊primarySources類(run方法存進來的配置類)
// 發布ApplicationPreparedEvent事件
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// refresh容器:解析配置類、掃描、啟動webServer。spring相關的邏輯!
refreshContext(context);
// 空方法,類似spring的onRefresh方法,可以由子類實現。
afterRefresh(context, applicationArguments);
// 啟動的事件
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 發布一個ApplicationStartedEvent事件,表示spring已經啟動完成
listeners.started(context, timeTakenToStartup);
// 從spring容器中獲取ApplicationRunner和CommandLineRunner,並執行他的run方法。
// 這倆個可以自己定義Bean
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 失敗之後,發布一個失敗的事件:ApplicationFailedEvent
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 計算下事件
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
// 一切都成功,發布ApplicationStartedEvent事件
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
// 失敗之後,發布一個失敗的事件:ApplicationFailedEvent
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
創建引導啟動器
private DefaultBootstrapContext createBootstrapContext() {
// 構建一個DefaultBootstrapContext對象,這個對象是2.4.0之後才會有!
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
// 利用bootstrapRegistryInitializers初始化bootstrapContext。可以在spring.factories中設置bootstrapRegistryInitializers
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
準備environment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment 這句話是源碼中本身的注釋!
// 創建ApplicationServletEnvironment。
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 添加SimpleCommandLinePropertySource放在首位
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 把所有的PropertySource封裝為ConfigurationPropertySourcesPropertySource,然後添加到environment中,放在首位!
ConfigurationPropertySources.attach(environment);
// 發布ApplicationEnvironmentPreparedEvent(應用Environment準備完成)事件,表示環境已經準備好了。默認EnvironmentPostProcessorApplicationListener去處理這個事件!
listeners.environmentPrepared(bootstrapContext, environment);
// 把DefaultProperties放到最後
DefaultPropertiesPropertySource.moveToEnd(environment);
// 環境中spring.main.environment-prefix參數校驗
Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
// 環境中spring.main參數校驗
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
準備spring容器的操作
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 設置環境變數到spring容器
context.setEnvironment(environment);
// 設置在SpringApplication上的BeanNameGenerator、resourceLoader設置到spring容器中
postProcessApplicationContext(context);
// 使用ApplicationContextInitializer初始化spring容器
applyInitializers(context);
// 容器初始化完成,發布ApplicationContextInitializedEvent事件
listeners.contextPrepared(context);
// 關閉引導的容器
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 註冊一些單例Bean
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// spring容器設置AllowCircularReferences和allowBeanDefinitionOverriding
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 拿到啟動配置(run方法傳遞進來的)
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 將啟動配置類解析為BeanDefinition註冊到spring容器
load(context, sources.toArray(new Object[0]));
// 發布ApplicationPreparedEvent事件,表示已經啟動好spring容器
listeners.contextLoaded(context);
}
結束語
- 你的點贊是我提高文章品質最大的動力!!!
- 獲取更多本文的前置知識文章,以及新的有價值的文章,讓我們一起成為架構師!
- 目前已經完成了並發編程、MySQL、spring源碼、Mybatis的源碼。可以在公眾號下方菜單點擊查看之前的文章!
- 這個公眾號的所有技術點,會分析的很深入!
- 這個公眾號,無廣告!!!
