目錄
2、使用類加載器加載ApplicationContextInitializer和ApplicationListener
二、SpringApplication的run方法(主要流程節點)
一、構造器初始化和run之前可以調整的配置
Spring Boot在執行SpringApplication的run方法之前,其實在構造函數中已經完成了很多初始化的準備操作,並且在run方法之前我們可以配置很多信息(上一篇博客中分析了很多的字段都是有Setter的)。比如我們可以將代碼寫作如下:
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(KevinToolApplication.class);
springApplication.setWebApplicationType(WebApplicationType.SERVLET);
springApplication.run(args);
}
先看看全參構造器本身都初始化了哪些組件或者配置:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 確定當前的ApplicationContext類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 設置ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 設置ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 確定main方法所在的類
this.mainApplicationClass = deduceMainApplicationClass();
}
一般情況下類加載器爲null,main方法所在的類則會傳入(比如當前爲KevinToolApplication.class)。
1、確定當前ApplicationContext類型
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
現在是幾個基礎判斷,後續可以通過Settter進行修改,最好根據createApplicationContext方法映射ApplicationContext類型。
1)、如果有DispatcherHandler,沒有DispatcherServlet和ServletContainer則創建REACTIVE類型。
2)、如果沒有ConfigurableWebApplicationContext類型,則創建NONE(非Web類型)。
3)、否則就創建SERVLET普通Web類型。
2、使用類加載器加載ApplicationContextInitializer和ApplicationListener
都會使用統一的加載方法getSpringFactoriesInstances進行處理:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 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;
}
1)、獲取類加載器(默認不會傳入)
這裏可以思考,同一個class文件,不同的類加載器加載完成後會得到兩個類。所以每有特殊情況的話最好不要傳入自己的類加載器,只是Spring Boot在這裏做了擴展點。
public ClassLoader getClassLoader() {
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
} // 省略部分代碼
return cl;
}
比較情況的看到,或當前線程的類加載器(AppClassLoader類型),保證整個系統中使用同一個類加載器。
2)、通過類加載器和名稱獲取類全限定名
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} // 省略部分代碼
}
首先在"META-INF/spring.factories"中獲取對於的配置信息,並且按照類加載器進行緩存。緩存對象cache的結構爲:Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
3)、根據類加載器、環境參數等信息反射生成類
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
直接通過反射創建對象。
4)、排序(按照@Order等)
所有使用該公共方法獲取的類型都可以添加@Order或@Priority或Ordered,最後都會使用java.util.Comparator的子類AnnotationAwareOrderComparator進行排序處理。
二、SpringApplication的run方法(主要流程節點)
public ConfigurableApplicationContext run(String... args) {
// 使用Spring自己的定時器工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設置Headless
configureHeadlessProperty();
// 獲取SpringApplicationRunListener類型
SpringApplicationRunListeners listeners = getRunListeners(args);
// SpringApplicationRunListener生命週期的 starting
listeners.starting();
try {
// 將main方法的args參數進行封裝,爲後續的CommandLineRunner回調做準備
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 準備Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner圖,就使用默認的SpringBootBanner即可
Banner printedBanner = printBanner(environment);
// 根據SpringApplication構造器出事化的WebApplicationType類型,反射創建ApplicationContext對象
context = createApplicationContext();
// 獲取SpringBootExceptionReporter類型
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// Spring的ApplicationContext的refresh前的準備工作(ApplicationContextInitializer完成初始回調,
// SpringApplicationRunListener生命週期的 contextLoaded)
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// Spring的ApplicationContext的refresh,ServletWebServerApplicationContext類型則會在onRefresh階段執行
// createWebServer完成內嵌Servlet容器的創建
refreshContext(context);
// refresh完成後的動作,默認爲空留給子類實現
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
// 如果需要打印日誌,則將StopWatch中的啓動信息進行打印
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// SpringApplicationRunListener生命週期的 started
listeners.started(context);
// 完成ApplicationRunner和CommandLineRunner的回調(主要用於與args相關的回調)
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 異常處理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// SpringApplicationRunListener生命週期的running
listeners.running(context);
}
catch (Throwable ex) {
// 異常處理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
之前說過AbstractApplicationContext的refresh分爲模板方法,那當前的run方法也爲模板方法,定義了執行流程。那麼,當前主要關注的流程節點有:
1)、SpringApplicationRunListener對應的生命週期,一直貫穿着整個的run方法
2)、prepareEnvironment(準備環境)
初始化ConfigurableEnvironment類型,並且賦值給ApplicationContext。可以參見:SpringIoc源碼(六)- ApplicationContext(二)- refresh(obtainFreshBeanFactory和StandardEnvironment)
3)、printBanner(打印Banner圖)
4)、createApplicationContext(反射創建ApplicationContext對象)
5)、SpringBootExceptionReporter(異常處理器的獲取)
爲了以後執行異常時,調用SpringBootExceptionReporter列表的回調方法。
6)、prepareContext(ApplicationContext的refresh前的準備工作)
ApplicationContext的refresh前的準備工作(ApplicationContextInitializer完成初始回調,SpringApplicationRunListener生命週期的 contextLoaded)
7)、refreshContext(ApplicationContext的refresh執行)
ApplicationContext的refresh,ServletWebServerApplicationContext類型則會在onRefresh階段執行。createWebServer完成內嵌Servlet服務器(如:Tomcat、Jetty)的創建。
8)、afterRefresh(模板空方法)
refresh完成後的動作,默認爲空留給子類實現
9)、如果需要打印日誌,則將StopWatch中的啓動信息進行打印
10)、callRunners(main方法args相關回調)
完成ApplicationRunner和CommandLineRunner的回調(主要用於與args相關的回調)
11)、handleRunFailure(中間有異常出現,異常邏輯回調)
詳細主要節點,後續進行分析