一個讀者,也是個人好朋友投稿的一篇關於 SpringBoot 啓動原理的文章,才大二就如此優秀,將來可期。前端
我一直想了解一下 SpirngBoot 的是如何啓動的,我想就來寫一篇關於 SpirngBoot 啓動分析吧。第一次寫那麼高深的技術話題理解不到位的話也請多多包涵。java
SpinrgBoot 2.0.2react
衆所周知 SpringBoot 的啓動類是在一個 main 方法中調用 SpringApplication.run()
方法啓動的,如:web
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DiveInSpringBootApplication.class, args);
}
}
複製代碼
啓動順序分析以下:算法
初始化階段 -> 運行階段
spring
進入run方法中,SpringApplication.run()
會先爲其建立一個 SpringApplication 對象:緩存
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//加載應用資源(URL資源、File資源、ClassPath資源)
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// primarySources 爲 run 方法傳入的引導類
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推斷Web應用類型
this.webApplicationType = deduceWebApplicationType();
//加載應用上下文(初始化Initializers)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//加載應用事件監聽器(初始化ApplicationListener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推斷引導類
this.mainApplicationClass = deduceMainApplicationClass();
}
複製代碼
Step1. 經過 deduceWebApplicationType()
來推斷咱們Web類型應用服務器
private WebApplicationType deduceWebApplicationType() {
//根據當前應用的ClassPath中是否存在相關實現類來推斷Web類型
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
複製代碼
看看使用到的 3 個常量值:app
常量值 | 應用類型 |
---|---|
REACTIVE_WEB_ENVIRONMENT_CLASS | org.springframework.web.reactive.DispatcherHandler |
MVC_WEB_ENVIRONMENT_CLASS | org.springframework.web.servlet.DispatcherServlet |
WEB_ENVIRONMENT_CLASSES | {"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" } |
也就是說,有以下三種狀況:less
若是應用程序中存在 org.springframework.web.reactive.DispatcherHandler 這個類,則表示是一個響應式 web 應用,項目在啓動時,須要去加載啓動內嵌的響應式 web 服務器。
若是應用程序中既不存在 javax.servlet.Servlet 類,也不存在org.springframework.web.context.ConfigurableWebApplicationContext 這個類,則 表示當前應用不是一個web應用,啓動時無需加載啓動內嵌的 web 服務器。
除上述兩種狀況外,則表示當前應用是一個 servlet 的 web 應用,啓動時須要加載啓動內嵌的 servlet 的 web 服務器(好比 Tomcat )。
Step2. 如何加載應用上下文初始器(初始化 Initializers )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
利用Spirng的工廠加載機制,實例化ApplicationContextInitializer實現類,並排序集合。具體實現方法以下:
SpringFactoriesLoader.loadFactoryNames
來掃描 META-INF/spring.factories
下符合 ApplicationContextInitializer 類型的資源名稱。META-INF/spring.factories
下找到的資源信息@order
註解和Ordered
接口進行排序# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#Spring容器的常見的錯誤配置警告
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#設置Spring應用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
複製代碼
Step3. 加載應用事件監聽器( ApplicationListener )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
利用 Spring 工廠加載機制,實例化 ApplicationListene r實現類,並排序對象集合,具體方法跟上面 初始化Initializers
相似,不贅述。
# Application Listeners
org.springframework.context.ApplicationListener=\
#Spring應用上下文加載完成以後清除緩存
org.springframework.boot.ClearCachesApplicationListener,\
#父容器關閉時通知各個子容器關閉,
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
#文件編碼
org.springframework.boot.context.FileEncodingApplicationListener,\
#控制檯彩色輸出
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
#外部化配置 管理factories或者YMAL文件
org.springframework.boot.context.config.ConfigFileApplicationListener,\
#將指定事件廣播給指定的監聽器
org.springframework.boot.context.config.DelegatingApplicationListener,\
#將須要輸出的日誌打印到指定的級別 DEBUG INFO ERROR
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
#初始化日誌系統
org.springframework.boot.context.logging.LoggingApplicationListener,\
#控制可執行Spirng文件版本
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製代碼
Step4. 推斷引導類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
//根據 Main 線程執行堆棧來判斷實際的引導類。
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
複製代碼
整個 SpringApplication 圍繞着 run 這個方法並分爲兩個小階段:
SpringApplication
運行監聽器,並監聽 Spring Boot
事件public ConfigurableApplicationContext run(String... args) {
//記錄運行時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//Spring 應用的上下文
ConfigurableApplicationContext context = null;
//記錄啓動期間的錯誤
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置文件加載及優先級判斷
configureHeadlessProperty();
//獲取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
//加載運行監聽器
listeners.starting();
try {
//建立ApplicationArguments對象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//加載屬性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = printBanner(environment);
//建立應用上下文
context = createApplicationContext();
//實例化SpringBootExceptionReporter用於報告啓動過程錯誤。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//初始化應用上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新應用上下文(IOC容器的準備,初始化Bean)
refreshContext(context);
//應用上下刷新完成以後
afterRefresh(context, applicationArguments);
stopWatch.stop();
//啓動日誌記錄器
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//啓動運行監聽器
listeners.started(context);
//啓動後須要的操做
callRunners(context, applicationArguments);
....
}
}
複製代碼
Step1. 加載SpringApplication
運行監聽器
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
複製代碼
利用 Spirng 的工廠加載機制,實例化 SpringApplicationRunListeners 實現類,並排序集合。具體實現方法以下:
經過 SpringFactoriesLoader.loadFactoryNames
來掃描 META-INF/spring.factories
下符合 SpringApplicationRunListeners 類型的資源名稱。
實例化全部在 META-INF/spring.factories
下找到的資源信息
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListenr
複製代碼
因而可知就只有 EventPublishingRunListenr
一個實現類
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
//實例化SimpleApplicationEventMulticaster事件發佈者
this.initialMulticaster = new SimpleApplicationEventMulticaster();
//以迭代的方法逐一進行ApplicationListener的監聽
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationStartedEvent(this.application, this.args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(this.application, this.args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,
this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
複製代碼
在 EventPublishingRunListener
實例化的時候,會實例化一個 SimpleApplicationEventMulticaster
事件發佈者(它的做用就是監聽容器中發佈的事件,只要事件發生,就觸發監聽器的回調,來完成事件驅動開發),因而接下來調用 listeners.starting()
方法就會經過其內部的 initialMulticaster
屬性發布 ApplicationStartingEvent
事件。
Step2. 建立Spirng應用上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//初始化階段的推斷Web類型
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製代碼
根據初始化階段的推斷Web應用類型來建立對應的 ConfigurableApplicationContext 實例
若是推斷的爲 SERVLETWeb 類型就實例化這個對象
web.servlet.context.AnnotationConfigServletWebServerApplicationContext
複製代碼
Step3. 建立 Environment
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
//建立 ConfigurableEnvironment 對象
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置 ConfigurableEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
//發佈 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
//將 ConfigurableEnvironment 綁定到 SpringApplication 中
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
複製代碼
根據初始化階段的推斷Web應用類型來建立對應的 ConfigurableEnvironment 實例。
至此整一個的 SpringBoot 過程已經分析完畢:咱們來總結一下:
首先初始化 SpringApplication 類,並推斷 WEB 啓動類型,再初始化和實現應用事件監聽器,而後推斷引導類。
經過 SpringFactoriesLoader 加載的 SpringApplicationRunListener,調用它們的 started 方法。
根據 Web 服務類型建立不一樣的 Spring 應用上下文,並將以前準備好的 Environment 設置給 Spring 應用上下文 ApplicationContext 使用。
建立並配置當前 Spring Boot 應用將要使用的 Environment,如 applocation.properties 文件和外部配置。
SpirngBoot 開始啓動。
若是本文對你哪怕有一丁點幫助,請幫忙點好看。你的好看是我堅持寫做的動力。 另外,關注以後在發送 1024 可領取免費學習資料。
資料詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、算法資料分享