上篇提到了啟動過程中的一些事件,這篇就來翻原始碼來了解下這些事件,廢話不多少,整起。。
SpringApplication.run(new Object[]{
WebApiThirdpartyApplication.class
}, args);
複製程式碼
每個springboot專案啟動都是從這開始,通過呼叫SpringApplication.run()
啟動,接下來看看run都做了啥
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
複製程式碼
例項SpringApplication,通過構造器去呼叫初始化方法
public SpringApplication(Object... sources) {
initialize(sources);
}
複製程式碼
上篇說事件的時候提到過 一個事件是在監聽註冊和初始化自後執行的,除了這兩個操作在任何處理之前
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼
接下來看下 這個初始化做了幾件事
this.webEnvironment = deduceWebEnvironment();
,判斷應用上下問環境
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
複製程式碼
其中WEB_ENVIRONMENT_CLASSES
是一個靜態常量陣列:
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
複製程式碼
可以看到是根據org.springframework.util.ClassUtils
的靜態方法去判斷classpath裡面是否有WEB_ENVIRONMENT_CLASSES
包含的類,如果有都包含則返回true
則表示啟動一個WEB應用,否則返回false
啟動一個標準Spring的應用。
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
這個方法則是初始化classpath下的所有的可用的ApplicationContextInitializer。
3. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
這個方法則是初使化classpath下的所有的可用的ApplicationListener。
4. this.mainApplicationClass = 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
方法來啟動應用。程式碼如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
複製程式碼
這裡我們看以下程式碼:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
複製程式碼
首先是獲取啟動時傳入引數args
並初始化為ApplicationArguments
物件,SpringApplication.run(Application.class, args);
取這裡傳入值。 然後配置SpringBoot應用的環境:
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
複製程式碼
Banner printedBanner = printBanner(environment);
列印標誌這個方法不說明,因為沒有什麼實質性作用,如果想玩玩修改一下標誌,那可以在專案的classpath下新建一個banner.txt檔案。
然後下面程式碼就是比較核心的:
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
複製程式碼
首先是createApplicationContext()
方法:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
thrownew IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
複製程式碼
可以看出根據之前初始化過程初始化的this.webEnvironment來決定初始化一個什麼容器。如果classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext類,
則使用DEFAULT_WEB_CONTEXT_CLASS
初始化容器,如果不存在則用DEFAULT_CONTEXT_CLASS
初始化容器。
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.
*/
publicstaticfinal String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
複製程式碼
以上是程式碼指定了容器的類名,最後通過Spring的工具類初始化容器類bean BeanUtils.instantiate(contextClass);
完成容器的建立工作。然後執行以下的幾個步驟完成整個容器的建立與啟動以及bean的注入功能。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
複製程式碼
以下這一句程式碼是實現spring-boot-starter-*的自動化配置的關鍵。
refreshContext(context); afterRefresh(context, applicationArguments);
至此通過SpringBoot啟動的容器已經構造完成。這裡忽略了啟動流程中的收集各種Listener,建立Environment及Environment的初始化的。