Spring Boot啟動流程詳解(一)
轉載:http://www.cnblogs.com/xinzhao/p/5551828.html
環境
本文基於Spring Boot版本1.3.3, 使用了spring-boot-starter-web。
配置完成後,編寫了程式碼如下:
@SpringBootApplication
public class Application {
publicstaticvoidmain(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
public class RootController {
public static final String PATH_ROOT = "/";
@RequestMapping(PATH_ROOT)
public String welcome() {
return "Welcome!";
}
}
雖然只有幾行程式碼,但是這已經是一個完整的Web程式,當訪問url的path部分為"/"時,返回字串"Welcome!"。
首先是一個非常普通的java程式入口,一個符合約定的靜態main方法。在這個main方法中,呼叫了SpringApplication的靜態run方法,並將Application類物件和main方法的引數args作為引數傳遞了進去。
然後是一個使用了兩個Spring註解的RootController類,我們在main方法中,沒有直接使用這個類。
SpringApplication類的靜態run方法
以下程式碼摘自:org.springframework.boot.SpringApplicationpublicstatic ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
publicstatic ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
在這個靜態方法中,建立SpringApplication物件,並呼叫該物件的run方法。
構造SpringApplication物件
以下程式碼摘自:org.springframework.boot.SpringApplicationpublicSpringApplication(Object... sources) {
initialize(sources);
}
privatevoidinitialize(Object[] sources) {
// 為成員變數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();
}
建構函式中呼叫initialize方法,初始化SpringApplication物件的成員變數sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的賦值比較簡單,就是我們傳給SpringApplication.run方法的引數。剩下的幾個,我們依次來看看是怎麼做的。
首先是webEnvironment:
以下程式碼摘自:org.springframework.boot.SpringApplication
private boolean webEnvironment;
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
privatevoidinitialize(Object[] sources) {
...
// 為成員變數webEnvironment賦值
this.webEnvironment = deduceWebEnvironment();
...
}
privatebooleandeduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
可以看到webEnvironment是一個boolean,該成員變數用來表示當前應用程式是不是一個Web應用程式。那麼怎麼決定當前應用程式是否Web應用程式呢,是通過在classpath中檢視是否存在WEB_ENVIRONMENT_CLASSES這個陣列中所包含的類,如果存在那麼當前程式即是一個Web應用程式,反之則不然。
在本文的例子中webEnvironment的值為true。
然後是initializers:
initializers成員變數,是一個ApplicationContextInitializer型別物件的集合。 顧名思義,ApplicationContextInitializer是一個可以用來初始化ApplicationContext的介面。
以下程式碼摘自:org.springframework.boot.SpringApplication
private List<ApplicationContextInitializer<?>> initializers;
privatevoidinitialize(Object[] sources) {
...
// 為成員變數initializers賦值
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
...
}
publicvoidsetInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
this.initializers.addAll(initializers);
}
可以看到,關鍵是呼叫getSpringFactoriesInstances(ApplicationContextInitializer.class),來獲取ApplicationContextInitializer型別物件的列表。
以下程式碼摘自:org.springframework.boot.SpringApplication
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在該方法中,首先通過呼叫SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取所有Spring Factories的名字,然後呼叫createSpringFactoriesInstances方法根據讀取到的名字建立物件。最後會將建立好的物件列表排序並返回。
以下程式碼摘自:org.springframework.core.io.support.SpringFactoriesLoader
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
publicstatic List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
可以看到,是從一個名字叫spring.factories的資原始檔中,讀取key為org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分內容如下:
以下內容摘自spring-boot-1.3.3.RELEASE.jar中的資原始檔META-INF/spring.factories
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
可以看到,最近的得到的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的名字。
接下來會呼叫createSpringFactoriesInstances來建立ApplicationContextInitializer例項。
以下程式碼摘自:org.springframework.boot.SpringApplication
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
T instance = (T) constructor.newInstance(args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
所以在我們的例子中,SpringApplication物件的成員變數initalizers就被初始化為,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的物件組成的list。
下圖畫出了載入的ApplicationContextInitializer,並說明了他們的作用。至於何時應用他們,且聽後面慢慢分解。
接下來是成員變數listeners
以下程式碼摘自:org.springframework.boot.SpringApplication
private List<ApplicationListener<?>> listeners;
privatevoidinitialize(Object[] sources) {
...
// 為成員變數listeners賦值
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
}
publicvoidsetListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<ApplicationListener<?>>();
this.listeners.addAll(listeners);
}
listeners成員變數,是一個ApplicationListener<?>型別物件的集合。可以看到獲取該成員變數內容使用的是跟成員變數initializers一樣的方法,只不過傳入的型別從ApplicationContextInitializer.class變成了ApplicationListener.class。
看一下spring.factories中的相關內容:
以下內容摘自spring-boot-1.3.3.RELEASE.jar中的資原始檔META-INF/spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
也就是說,在我們的例子中,listener最終會被初始化為ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener這幾個類的物件組成的list。
下圖畫出了載入的ApplicationListener,並說明了他們的作用。至於他們何時會被觸發,等事件出現時,我們再說明。
最後是mainApplicationClass
以下程式碼摘自:org.springframework.boot.SpringApplication
private Class<?> mainApplicationClass;
privatevoidinitialize(Object[] sources) {
...
// 為成員變數mainApplicationClass賦值
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;
}
在deduceMainApplicationClass方法中,通過獲取當前呼叫棧,找到入口方法main所在的類,並將其複製給SpringApplication物件的成員變數mainApplicationClass。在我們的例子中mainApplicationClass即是我們自己編寫的Application類。
SpringApplication物件的run方法
經過上面的初始化過程,我們已經有了一個SpringApplication物件,根據SpringApplication類的靜態run方法一節中的分析,接下來會呼叫SpringApplication物件的run方法。我們接下來就分析這個物件的run方法。
以下程式碼摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
context = createAndRefreshContext(listeners, applicationArguments);
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, ex);
throw new IllegalStateException(ex);
}
}
-
可變個數引數args即是我們整個應用程式的入口main方法的引數,在我們的例子中,引數個數為零。
-
StopWatch是來自org.springframework.util的工具類,可以用來方便的記錄程式的執行時間。
SpringApplication物件的run方法建立並重新整理ApplicationContext,算是開始進入正題了。下面按照執行順序,介紹該方法所做的工作。
headless模式
以下程式碼摘自:org.