前言
還是從SpringBoot的啟動類說起,這篇文章主要分析啟動類中的SpringApplication
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製程式碼
可以看出main函式中重要的就是SpringApplication.run()
,這可以分為兩部分來探討:
- SpringApplication的構造過程
- SpringApplication的run()方法
SpringApplication的初始化
首先進入SpringApplication的建構函式,先是單個引數的構造方法,後進入兩個引數的構造方法,ResourceLoader是Spring的資源載入器,這裡沒有自定義的ResourceLoader傳入,所以為NULL,而primarySources引數就是我們傳入的Application.class啟動類
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1. 推斷應用型別
this.webApplicationType = deduceWebApplicationType();
//2. initializer初始化模組,載入ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//3. 載入監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4. 配置應用main方法所在的類
this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼
SpringApplication的初始化主要包括以下4個步驟:
- 推斷應用型別
- 載入初始化構造器ApplicationContextInitializer
- 建立應用監聽器
- 設定應用main()方法所在的類
1. 推斷應用型別
this.webEnvironment=deduceWebApplicationType(); 判斷應用的型別,是否是servlet應用還是reactive應用或者是none,webEnvironment中定義了這三種型別
2. 載入初始化構造器ApplicationContextInitializer
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 通過SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationContextInitializer
進入loadFactoryNames方法,然後進入loadSpringFactories方法,獲取當前ClassLoader下的所有META-INF/spring.factories檔案的配置資訊
而後通過loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 從所有META-INF/spring.factories檔案的配置資訊的map中獲取指定的factory的值
預設情況下,從 spring.factories 檔案找出的 key 為 ApplicationContextInitializer 的類有如上圖中所示4種
對於 ApplicationContextInitializer,它是應用程式初始化器,做一些初始化工作
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
複製程式碼
3. 建立應用監聽器
setListeners()方法與setInitializers()方法類似,只不過它是使用SpringFactoriesLoader在應用的classpath的META-INT/spring.factories中查詢並載入所有可用的ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
複製程式碼
ApplicationListener,應用程式事件(ApplicationEvent)監聽器:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
複製程式碼
更詳細的分析可以參閱我之前的文章: springboot系列文章之啟動時初始化資料
4. 設定應用main()方法所在的類
在SpringApplication建構函式的最後一步,根據呼叫棧推斷並設定main方法的定義類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
複製程式碼
SpringApplication的run方法
SpringApplication例項初始化完成並且完成設定後,就可以開始run方法的邏輯了,對於這個run方法我將分為以下幾點進行逐步剖析,而StopWatch是一個工具類,主要是方便記錄程式執行時間,這裡就不仔細介紹了。
public ConfigurableApplicationContext run(String... args) {
//構造一個任務執行觀察期
StopWatch stopWatch = new
StopWatch();
//開始執行,記錄開始時間
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//1
configureHeadlessProperty();
//2
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//3
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//4
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//5
configureIgnoreBeanInfo(environment);
//6
Banner printedBanner = printBanner(environment);
//7
context = createApplicationContext();
//8
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//9
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//10
refreshContext(context);
//2.0版本中是空實現
//11
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//12
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;
}
複製程式碼
SpringApplication的run方法主要分為以下幾步:
- Headless模式設定
- 載入SpringApplicationRunListeners監聽器
- 封裝ApplicationArguments物件
- 配置環境模組
- 根據環境資訊配置要忽略的bean資訊
- Banner配置SpringBoot彩蛋
- 建立ApplicationContext應用上下文
- 載入SpringBootExceptionReporter
- ApplicationContext基本屬性配置
- 更新應用上下文
- 查詢是否註冊有CommandLineRunner/ApplicationRunner
1. Headless模式設定
configureHeadlessProperty()
設定 headless 模式,即設定系統屬性java.awt.headless,它是J2SE的一種模式,用於在缺少螢幕,鍵盤,或者滑鼠時的系統配置,該屬性會被設定為true,更多的資訊可以參考這裡
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
...
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
複製程式碼
2. 載入SpringApplicationRunListeners監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
複製程式碼
getRunListeners(args)
也是通過 SpringFactoriesLoader
從META-INF/spring.factories
查詢到並載入的SpringApplicationRunListener
。該類實際上是監聽SpringApplication的run方法的執行
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,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//通過SpringFactoriesLoader可以查詢到並載入的SpringApplicationRunListner
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製程式碼
這裡的SpringApplicationRunListener監聽器與SpringApplication時載入的ApplicationListener監聽器不同,SpringApplicationRunListener是SpringBoot新增的類,SpringApplicationRunListener目前只有一個實現類EventPublishingRunListener。雖然說是新增的, 但是它們之間是有聯絡的,它們之間的的關係是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯絡起來的
更詳細的分析請參閱 :SpringBoot原始碼分析之SpringBoot的啟動過程
3. 封裝ApplicationArguments物件
將args引數封裝成 ApplicationArguments
物件
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
複製程式碼
官網對 ApplicationArguments
的解釋如下
4. 配置環境模組
根據listeners
和applicationArguments
建立並配置當前SpringBoot應用將要使用的Enviroment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
複製程式碼
遍歷呼叫所有SpringApplicationRunListener的enviromentPrepared()
方法就是宣告當前SpringBoot應用使用的Enviroment準備好了
5. 根據環境資訊配置要忽略的bean資訊
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
Boolean.class, Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
複製程式碼
6. Banner配置SpringBoot彩蛋
列印banner標誌,就是啟動SpringBoot專案時出現的Spring
字樣,當然我們也可以自定義banner,這裡就不多說了
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
複製程式碼
7. 建立ApplicationContext應用上下文
createApplicationContext()
根據使用者是否明確設定了applicationContextClass型別以及SpringApplication初始化階段的推斷結果,決定該為當前SpringBoot應用建立什麼型別的ApplicationContext並建立完成。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
protected ConfigurableApplicationContext createApplicationContext() {
//使用者是否明確設定了applicationContextClass,在SpringApplication中有對應的setter方法
Class<?> contextClass = this.applicationContextClass;
//如果沒有主動設定
if (contextClass == null) {
try {
//判斷當前應用的型別,也就是之前SpringApplication初始化階段的推斷結果
switch (this.webApplicationType) {
//servlet應用程式
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
//reactive響應式程式
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);
}
複製程式碼
在SpringBoot官網對ApplicationContext的型別是如下定義的:
- 當SpringMVC存在的時候,就使用AnnotationConfigServletWebServerApplicationContext
- 當SpringMVC不存在的時候,Spring WebFlux響應式存在的時候,使用AnnotationConfigReactiveWebServerApplicationContext
- 如果以上都不是,預設就用AnnotationConfigApplicationContext
- SpringApplication存在設定ApplicationContext的方法,在JUnit測試中使用SpringApplication通常要設定ApplicationContext
8. 載入SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
複製程式碼
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;
}
複製程式碼
這裡也是通過SpringFactoriesLoader載入META-INF/spring.factories中key為SpringBootExceptionReporter的全類名的value值
SpringBootExceptionReporter
是一個回撥介面,用於支援對SpringApplication
啟動錯誤的自定義報告。裡面就一個報告啟動失敗的方法- 其實現類:
org.springframework.boot.diagnostics.FailureAnalyzers
用於觸發從spring.factories載入的FailureAnalyzer
和FailureAnalysisReporter
例項
9. ApplicationContext基本屬性配置
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//設定應用的環境
context.setEnvironment(environment);
//對 context 進行了預設定
postProcessApplicationContext(context);
applyInitializers(context);
遍歷呼叫SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot應用使用的ApplicationContext準備好了
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//遍歷呼叫SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext裝填完畢
listeners.contextLoaded(context);
}
複製程式碼
1). applyInitializers(context);
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
複製程式碼
遍歷呼叫這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理
2). load(ApplicationContext context, Object[] sources)
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
複製程式碼
設定資源載入器,載入各種beans到ApplicationContext物件中
10. 更新應用上下文
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
複製程式碼
進入內部的refresh()方法,準備環境所需的bean工廠,通過工廠產生環境所需的bean,重點就是產生bean
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
複製程式碼
11. afterRefresh()
上下文重新整理後呼叫該方法,目前沒有操作
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
複製程式碼
12. callRunner()
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);
}
}
}
複製程式碼
查詢當前的ApplicationContext中是否註冊有CommandLineRunner或者ApplicationRunner,如果有,就遍歷執行他們。
SpringBoot啟動流程總結
上面從SpringApplication的初始化到SpringApplication.run()方法執行,基本上按照其內部函式呼叫的順序一步一步分析下來,內容非常多,很容易把人搞暈。在網上發現一張圖,圖出自SpringBoot啟動流程解析,畫的比較清楚明白,把SpringBoot啟動整個流程都包含進來了
再總結下run方法中最關鍵的幾步:
- 載入SpringApplicationRunListeners監聽器
- 配置環境模組
- 建立ApplicationContext應用上下文
- ApplicationContext基本屬性配置
- 更新應用上下文,產生環境所需要的bean
小結
上面的分析都是基於SpringBoot2.0版本,在之前的版本,內容上可能有些偏差,大體思路是差不多的。在閱讀原始碼的過程中,看了很多前人分析的部落格文章,也借鑑了他們的分析流程,有點「前人栽樹,後人乘涼」的感覺,現在「取之網路,再回饋之網路」