參考:
http://www.reibang.com/p/d51e1896a5f7
http://412887952-qq-com.iteye.com/blog/2345379
1.程序入口代碼:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2.進入之后會進入到SpringApplication.java類中:
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
3.進入重構run()方法:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
4.這里new了一個SpringApplication對象出來蛹尝,然后調用其run方法氯夷,我們先看看這個new SpringApplication的過程:
public SpringApplication(Object... sources) {
initialize(sources); // sources目前是一個MyApplication的class對象
}
5.看下具體的initialize()方法:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources)); // 把sources設置到SpringApplication的sources屬性中,目前只是一個MyApplication類對象
}
this.webEnvironment = deduceWebEnvironment(); // 判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在)渠退,并設置到webEnvironment屬性中
// 從spring.factories文件中找出key為ApplicationContextInitializer的類并實例化后設置到SpringApplication的initializers屬性中庇忌。這個過程也就是找出所有的應用程序初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 從spring.factories文件中找出key為ApplicationListener的類并實例化后設置到SpringApplication的listeners屬性中睛挚。這個過程就是找出所有的應用程序事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找出main類,這里是MyApplication類
this.mainApplicationClass = deduceMainApplicationClass();
}
ApplicationContextInitializer枷遂,應用程序初始化器樱衷,做一些初始化的工作:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
ApplicationListener,應用程序事件(ApplicationEvent)監(jiān)聽器:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
注意:此處的ApplicationListener有別于下面的SpringApplicationRunListener
這里的應用程序事件(ApplicationEvent)有應用程序啟動事件(ApplicationStartedEvent)酒唉,失敗事件(ApplicationFailedEvent)矩桂,準備事件(ApplicationPreparedEvent)等。
應用程序事件監(jiān)聽器跟監(jiān)聽事件是綁定的痪伦。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定侄榴,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等网沾。
默認情況下癞蚕,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:
org.springframework.boot.context.config.DelegatingApplicationContextInitializer
org.springframework.boot.context.ContextIdApplicationContextInitializer
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
key為ApplicationListener的有:
org.springframework.boot.context.config.ConfigFileApplicationListener
org.springframework.boot.context.config.AnsiOutputApplicationListener
org.springframework.boot.logging.LoggingApplicationListener
org.springframework.boot.logging.ClasspathLoggingApplicationListener
org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.context.config.DelegatingApplicationListener
org.springframework.boot.builder.ParentContextCloserApplicationListener
org.springframework.boot.context.FileEncodingApplicationListener
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
從上面的代碼我們看到初始化做了以下幾件事情:
5.1 this.webEnvironment = deduceWebEnvironment();
這一個方法決定創(chuàng)建的是一個WEB應用還是一個SPRING的標準Standalone應用。如果入方法可以看到其是怎么判斷的:
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
其中WEB_ENVIRONMENT_CLASSES是一個靜態(tài)常量數(shù)組:
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
可以看到是根據(jù)org.springframework.util.ClassUtils的靜態(tài)方法去判斷classpath里面是否有WEB_ENVIRONMENT_CLASSES包含的類辉哥,如果有都包含則返回true則表示啟動一個WEB應用桦山,否則返回false啟動一個標準Spring的應用。
是否啟動一個WEB應用就是取決于classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext醋旦。
5.2 進入下一個階段:
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
}
這個方法則是初始化classpath下的所有的可用的ApplicationContextInitializer
5.3 下一步:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
這個方法則是初使化classpath下的所有的可用的ApplicationListener
5.4 下一步:
this.mainApplicationClass = deduceMainApplicationClass();
我們找到對應的deduceMainApplicationClass()方法:
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;
}
}
最后找出main方法的全類名并返回其實例并設置到SpringApplication的this.mainApplicationClass完成初始化恒水。然后調用SpringApplication實例的run方法來啟動應用
SpringApplication的執(zhí)行
分析run方法之前,先看一下SpringApplication中的一些事件和監(jiān)聽器概念饲齐。
首先是SpringApplicationRunListeners類和SpringApplicationRunListener類的介紹钉凌。
SpringApplicationRunListeners內部持有SpringApplicationRunListener集合和1個Log日志類。用于SpringApplicationRunListener監(jiān)聽器的批量執(zhí)行捂人。
SpringApplicationRunListener看名字也知道用于監(jiān)聽SpringApplication的run方法的執(zhí)行御雕。
它定義了5個步驟:
- started(run方法執(zhí)行的時候立馬執(zhí)行矢沿;對應事件的類型是ApplicationStartedEvent)
- environmentPrepared(ApplicationContext創(chuàng)建之前并且環(huán)境信息準備好的時候調用;對應事件的類型是ApplicationEnvironmentPreparedEvent)
- contextPrepared(ApplicationContext創(chuàng)建好并且在source加載之前調用一次酸纲;沒有具體的對應事件)
- contextLoaded(ApplicationContext創(chuàng)建并加載之后并在refresh之前調用捣鲸;對應事件的類型是ApplicationPreparedEvent)
- finished(run方法結束之前調用;對應事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)
SpringApplicationRunListener目前只有一個實現(xiàn)類EventPublishingRunListener闽坡,它把監(jiān)聽的過程封裝成了SpringApplicationEvent事件并讓內部屬性(屬性名為multicaster)ApplicationEventMulticaster接口的實現(xiàn)類SimpleApplicationEventMulticaster廣播出去摄狱,廣播出去的事件對象會被SpringApplication中的listeners屬性進行處理。
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
所以說SpringApplicationRunListener和ApplicationListener之間的關系是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯(lián)系起來的无午。
SpringApplication的run方法代碼如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // 構造一個任務執(zhí)行觀察器
stopWatch.start(); // 開始執(zhí)行,記錄開始時間
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 獲取SpringApplicationRunListeners祝谚,內部只有一個EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 上面分析過宪迟,會封裝成SpringApplicationEvent事件然后廣播出去給SpringApplication中的listeners所監(jiān)聽
// 這里接受ApplicationStartedEvent事件的listener會執(zhí)行相應的操作
listeners.started();
try {
// 構造一個應用程序參數(shù)持有類
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 創(chuàng)建Spring容器
context = createAndRefreshContext(listeners, applicationArguments);
// 容器創(chuàng)建完成之后執(zhí)行額外一些操作
afterRefresh(context, applicationArguments);
// 廣播出ApplicationReadyEvent事件給相應的監(jiān)聽器執(zhí)行
listeners.finished(context, null);
stopWatch.stop(); // 執(zhí)行結束,記錄執(zhí)行時間
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context; // 返回Spring容器
}
catch (Throwable ex) {
handleRunFailure(context, listeners, ex); // 這個過程報錯的話會執(zhí)行一些異常操作交惯、然后廣播出ApplicationFailedEvent事件給相應的監(jiān)聽器執(zhí)行
throw new IllegalStateException(ex);
}
}
創(chuàng)建容器的方法createAndRefreshContext如下:
private ConfigurableApplicationContext createAndRefreshContext(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableApplicationContext context; // 定義Spring容器
// 創(chuàng)建應用程序的環(huán)境信息次泽。如果是web程序,創(chuàng)建StandardServletEnvironment席爽;否則意荤,創(chuàng)建StandardEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置一些環(huán)境信息。比如profile只锻,命令行參數(shù)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 廣播出ApplicationEnvironmentPreparedEvent事件給相應的監(jiān)聽器執(zhí)行
listeners.environmentPrepared(environment);
// 環(huán)境信息的校對
if (isWebEnvironment(environment) && !this.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
if (this.bannerMode != Banner.Mode.OFF) { // 是否在控制臺上打印自定義的banner
printBanner(environment);
}
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext(); // 創(chuàng)建Spring容器
context.setEnvironment(environment); // 設置Spring容器的環(huán)境信息
postProcessApplicationContext(context); // 回調方法玖像,Spring容器創(chuàng)建之后做一些額外的事
applyInitializers(context); // SpringApplication的的初始化器開始工作
// 遍歷調用SpringApplicationRunListener的contextPrepared方法。目前只是將這個事件廣播器注冊到Spring容器中
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 把應用程序參數(shù)持有類注冊到Spring容器中齐饮,并且是一個單例
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
// 廣播出ApplicationPreparedEvent事件給相應的監(jiān)聽器執(zhí)行
listeners.contextLoaded(context);
// Spring容器的刷新
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
return context;
}
Spring容器的創(chuàng)建createApplicationContext方法如下:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 如果是web程序捐寥,那么構造org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext容器
// 否則構造org.springframework.context.annotation.AnnotationConfigApplicationContext容器
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
Spring容器創(chuàng)建之后有個回調方法postProcessApplicationContext:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.webEnvironment) { // 如果是web程序
if (context instanceof ConfigurableWebApplicationContext) { // 并且也是Spring Web容器
ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
if (this.beanNameGenerator != null) { // 如果SpringApplication設置了是實例命名生成器,注冊到Spring容器中
configurableContext.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
}
}
if (this.resourceLoader != null) { // 如果SpringApplication設置了資源加載器祖驱,設置到Spring容器中
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
}
初始化器做的工作
比如ContextIdApplicationContextInitializer會設置應用程序的id握恳;AutoConfigurationReportLoggingInitializer會給應用程序添加一個條件注解解析器報告等:
protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍歷每個初始化器,對調用對應的initialize方法
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
Spring容器的刷新refresh方法內部會做很多很多的事情:
比如BeanFactory的設置捺僻,BeanFactoryPostProcessor接口的執(zhí)行乡洼、BeanPostProcessor接口的執(zhí)行、自動化配置類的解析匕坯、條件注解的解析束昵、國際化的初始化等等。這部分內容會在之后的文章中進行講解醒颖。
run方法中的Spring容器創(chuàng)建完成之后會調用afterRefresh方法
代碼如下:
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
afterRefresh(context, args.getSourceArgs()); // 目前是個空實現(xiàn)
callRunners(context, args); // 調用Spring容器中的ApplicationRunner和CommandLineRunner接口的實現(xiàn)類
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
// 找出Spring容器中ApplicationRunner接口的實現(xiàn)類
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 找出Spring容器中CommandLineRunner接口的實現(xiàn)類
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 對runners進行排序
AnnotationAwareOrderComparator.sort(runners);
// 遍歷runners依次執(zhí)行
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) { // 如果是ApplicationRunner妻怎,進行ApplicationRunner的run方法調用
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) { // 如果是CommandLineRunner,進行CommandLineRunner的run方法調用
callRunner((CommandLineRunner) runner, args);
}
}
}
這樣run方法執(zhí)行完成之后泞歉。Spring容器也已經(jīng)初始化完成逼侦,各種監(jiān)聽器和初始化器也做了相應的工作匿辩。
總結
SpringBoot啟動的時候,不論調用什么方法榛丢,都會構造一個SpringApplication的實例铲球,然后調用這個實例的run方法,這樣就表示啟動SpringBoot晰赞。
在run方法調用之前稼病,也就是構造SpringApplication的時候會進行初始化的工作,初始化的時候會做以下幾件事:
- 把參數(shù)sources設置到SpringApplication屬性中掖鱼,這個sources可以是任何類型的參數(shù)然走。本文的例子中這個sources就是MyApplication的class對象
- 判斷是否是web程序,并設置到webEnvironment這個boolean屬性中
- 找出所有的初始化器戏挡,默認有5個芍瑞,設置到initializers屬性中
- 找出所有的應用程序監(jiān)聽器,默認有9個褐墅,設置到listeners屬性中
- 找出運行的主類(main class)
SpringApplication構造完成之后調用run方法拆檬,啟動SpringApplication,run方法執(zhí)行的時候會做以下幾件事:
- 構造一個StopWatch妥凳,觀察SpringApplication的執(zhí)行
- 找出所有的SpringApplicationRunListener并封裝到SpringApplicationRunListeners中竟贯,用于監(jiān)聽run方法的執(zhí)行。監(jiān)聽的過程中會封裝成事件并廣播出去讓初始化過程中產(chǎn)生的應用程序監(jiān)聽器進行監(jiān)聽
- 構造Spring容器(ApplicationContext)逝钥,并返回
3.1 創(chuàng)建Spring容器的判斷是否是web環(huán)境屑那,是的話構造AnnotationConfigEmbeddedWebApplicationContext,否則構造AnnotationConfigApplicationContext
3.2 初始化過程中產(chǎn)生的初始化器在這個時候開始工作
3.3 Spring容器的刷新(完成bean的解析艘款、各種processor接口的執(zhí)行齐莲、條件注解的解析等等) - 從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現(xiàn)類并排序后依次執(zhí)行