97. SpringBoot-啟動流程分析第一篇
感謝網友給我的打賞,感謝Leonzhang的支付寶打賞,非常感謝!
《從零開始學Spring Boot》系列部落格已經介紹了不少篇幅了,有些博友說要介紹下理論的知識,確實本系列部落格都是偏實現類的文章,這節就介紹下理論的知識吧。我們就來一起看看Spring Boot啟動流程。
我們找到我們程式的入口程式碼:
public static void main(String[] args) {
/*
* 在main方法進行啟動我們的應用程式.
*/
SpringApplication.run(App.class, args);
}
上面核心程式碼是:SpringApplication.run
我們進入之後會進入到SpringApplication.java類中:
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param source the source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
進入重構run()方法:
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
這裡new了一個SpringApplication物件出來,然後呼叫其run方法,我們先看看這個new SpringApplication的過程:
public SpringApplication(Object... sources) {
initialize(sources);
}
我們看下具體的initialize()方法:
@SuppressWarnings({ "unchecked", "rawtypes" })
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();
這一個方法決定建立的是一個WEB應用還是一個SPRING的標準Standalone應用。如果入方法可以看到其是怎麼判斷的:
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
其中WEB_ENVIRONMENT_CLASSES是一個靜態常量陣列:
privatestaticfinal String[] WEB_ENVIRONMENT_CLASSES = {"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
可以看到是根據org.springframework.util.ClassUtils的靜態方法去判斷classpath裡面是否有WEB_ENVIRONMENT_CLASSES包含的類,如果有都包含則返回true則表示啟動一個WEB應用,否則返回false啟動一個標準Spring的應用。
可以看到是否啟動一個WEB應用就是取決於classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext。
進入下一個階段:
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
這個方法則是初始化classpath下的所有的可用的ApplicationContextInitializer
下一步:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
這個方法則是初使化classpath下的所有的可用的ApplicationListener
下一步:
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方法來啟動應用,程式碼如下:
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的初始化的因為這些地方都是SpringBoot提供的各種擴充套件點,後面的部落格會詳細的說明各個擴充套件點的用處以及擴充套件的方式。
我們下節部落格會更詳細的介紹Spring Boot怎麼內嵌Tomcat的。