SpringBoot啟動流程原始碼分析(1)
SpringApplication例項化
springboot簡化spring的大量xml的配置檔案,為了實現統一化配置管理,一般引數配置 .properties檔案或者.yml檔案,統一採用main方法來進行啟動,內部集成了tomcat,實現安全監控,並且支援熱部署。
`
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
複製程式碼
調run方法之後,我們其實發現先去建立SpringApplication的例項。
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);
}
複製程式碼
`
接下來我們來繼續看構建SpringApplication例項的時候,做了什麼具體細節。
`
public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources,"PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推斷應用型別,後面會根據型別初始化對應的環境。常用的一般都是servlet環境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
////初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//初始化classpath下所有已配置的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//根據呼叫棧,推斷出main方法的類名
this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼
`
獲取ApplicationContextInitializer例項的物件,會呼叫initialize方法。
`
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes,Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type,classLoader));
//建立springFactories例項
List<T> instances = createSpringFactoriesInstances(type,parameterTypes,classLoader,args,names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
`
獲取ApplicationListener例項的物件,其中onApplicationEvent可以監聽全部事件。
`
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,classLoader));
List<T> instances = createSpringFactoriesInstances(type,names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
`
SpringApplication的run方法分析,啟動spring容器,載入bean。
`
public ConfigurableApplicationContext run(String... args) {
//記錄程式執行時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//ConfigurableApplicationContext Spring上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//從META-INF/spring.factories中獲取監聽器
//1、獲取並啟動監聽器。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2、構建應用上下文環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
//列印banner
Banner printedBanner = printBanner(environment);
//3、初始化應用上下文
context = createApplicationContext();
//例項化SpringBootExceptionReporter.class,用來支援報告
//關於啟動報錯
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class },context);
//4、重新整理應用上下文前的準備階段
prepareContext(context,environment,listeners,applicationArguments,printedBanner);
//5、重新整理應用上下文
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,null);
throw new IllegalStateException(ex);
}
return context;
}
複製程式碼
`
我們這裡來說明一下SpringApplication的run方法做了什麼事情。
第一步:獲取並啟動監聽器
第二步:構造應用上下文環境
第三步:初始化應用上下文
第四步:重新整理應用上下文前的準備階段
第五步:重新整理應用上下文
第六步:重新整理應用上下文後的擴充套件介面
①、啟動監聽器SpringApplicationRunListeners
`
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class,String[].class };
return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class,types,this,args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
`
小夥伴有沒有發現跟上面的ApplicationListener載入業務邏輯一樣。EventPublishingRunListener開始監聽事件。
②、構造應用上下文環境
構建應用上下文環境是去載入springboot的*.properties或者*.yml檔案。 `
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
// Create and configure the environment
//建立並且配置相應的環境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//根據使用者配置,配置environment系統環境
configureEnvironment(environment,applicationArguments.getSourceArgs());
//啟動相應的監聽器,其中一個重要的監聽器 //ConfigFileApplicationListener 就是載入專案配置檔案的監聽器。
listeners.environmentPrepared(environment);
//繫結springbootApplication環境
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
複製程式碼
`
③、初始化應用上下文
springboot應用上下文的環境,我們以SERVLET環境為例。
`
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
複製程式碼
`
下面是獲取context上下文例項。 `
/**
* The class name of application context that will be used by default for non-web
* environments.
*/
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/**
* The class name of application context that will be used by default for web
* environments.
*/
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/**
* The class name of application context that will be used by default for reactive web
* environments.
*/
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
//獲取context例項
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_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應用構建上下文預設為AnnotationConfigServletWebServerApplicationContext。我們可以來看一下AnnotationConfigServletWebServerApplicationContext的例項圖。
此圖的繼承關係如下:
AnnotationConfigServletWebServerApplicationContext類繼承了ServletWebServerApplicationContext
ServletWebServerApplicationContext繼承了GenericWebApplicationContext
GenericWebApplicationContext繼承了GenericApplicationContext
GenericApplicationContext繼承了AbstractApplicationContext
AbstractApplicationContext繼承了DefaultResourceLoader
應用上下文可以理解成容器的高階表現形式,應用上下文確實在IOC容器基礎上豐富了一些高階功能。