在上一篇,咱們主要介紹了,註解@SpringbootApplication的自動化配置原理,那麼首先咱們先回顧一下,這個註解主要爲咱們的sprngboot工程作了什麼: 咱們能夠將自動配置的關鍵幾步以及相應的註解總結以下:java
1.@Configuration&與@Bean------>>>基於java代碼的bean配置2.@Conditional-------->>>>>>設置自動配置條件依賴react
3.@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉換爲bean。web
4.@EnableAutoConfiguration、@AutoConfigurationPackage與@Import->實現bean發現與加載。 spring
今天,咱們來經過源碼來分析一下它的啓動過程.編程
本篇基於 2.0.4.RELEASE 版本進行分析,閱讀本文須要有一些 Java 和 Spring 框架基礎,若是還不知道 Spring Boot 是什麼,建議先看下官網的 Spring Boot 教程。springboot
上面是 Spring Boot 最簡單通用的入口類。入口類的要求是最頂層包下面第一個含有 main 方法的類,使用註解 @SpringBootApplication 來啓用 Spring Boot 特性,使用 SpringApplication.run 方法來啓動 Spring Boot 項目。app
首先來看一下這個類裏面run方法的調用源碼:框架
第一個參數 primarySource:加載的主要資源類;第二個參數 args:傳遞給應用的應用參數。spring-boot
先用主要資源類來實例化一個 SpringApplication 對象,再調用這個對象的 run 方法,因此咱們分兩步來分析這個啓動源碼。源碼分析
跟着上面的run進入到下面的方法:
進入SpringApplication能夠看到如下源碼:
從上面的源碼能夠知道,整個實例化過程有7個步驟:
this.resourceLoader = resourceLoader;
複製代碼
Assert.notNull(primarySources, "PrimarySources must not be null");
複製代碼
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
複製代碼
this.webApplicationType = WebApplicationType.deduceFromClasspath();
複製代碼
這裏進去WebApplicationType方法看一下源碼以及相關構造方法:
public enum WebApplicationType {
/** * The application should not run as a web application and should not start an * embedded web server. */
NONE,
/** * The application should run as a servlet-based web application and should start an * embedded servlet web server. */
SERVLET,
/** * The application should run as a reactive web application and should start an * embedded reactive web server. */
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
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;
}
static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.SERVLET;
}
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.REACTIVE;
}
return WebApplicationType.NONE;
}
private static boolean isAssignable(String target, Class<?> type) {
try {
return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
}
catch (Throwable ex) {
return false;
}
}
}
複製代碼
這個就是根據類路徑下是否有對應項目類型的類推斷出不一樣的應用類型,這裏也說明 Spring Boot 2是支持響應式編程。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
複製代碼
進入ApplicationContextInitializer,咱們能夠知道其做用:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/** * Initialize the given application context. * @param applicationContext the application to configure */
void initialize(C applicationContext);
}
複製代碼
用來初始化指定的 Spring 應用上下文,如註冊屬性資源、激活 Profiles 等。
再來看下 setInitializers 方法源碼,其實就是初始化一個 ApplicationContextInitializer 應用上下文初始化器實例的集合。
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
複製代碼
最後咱們來看一下核心方法getSpringFactoriesInstances 其源碼以下:
private <T> Collection<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<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
設置應用上下文初始化器可分爲如下 5 個步驟。 這裏是實例化的核心:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
複製代碼
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
複製代碼
loadFactoryNames 的源碼以下:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
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 ?
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 factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
複製代碼
根據類路徑下的 META-INF/spring.factories 文件解析並獲取 ApplicationContextInitializer 接口的全部配置的類路徑名稱。
spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories 的初始化器相關配置內容以下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
......
複製代碼
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
複製代碼
AnnotationAwareOrderComparator.sort(instances);
複製代碼
return instances;
複製代碼
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
複製代碼
ApplicationListener 的做用是什麼?源碼以下。(有空再寫一篇springboot2 監聽器的應用)
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/** * Handle an application event. * @param event the event to respond to */
void onApplicationEvent(E event);
}
複製代碼
看源碼,這個接口繼承了 JDK 的 java.util.EventListener 接口,實現了觀察者模式,它通常用來定義感興趣的事件類型,事件類型限定於 ApplicationEvent 的子類,這一樣繼承了 JDK 的 java.util.EventObject 接口。
設置監聽器和設置初始化器調用的方法是同樣的,只是傳入的類型不同,設置監聽器的接口類型爲:getSpringFactoriesInstances,對應的 spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories 文件配置內容在上面的配置文件裏面: Application Listeners
經過文件能夠看出當前只有一個 BackgroundPreinitializer 監聽器。
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 的棧幀,歷來獲得入口類的名字再返回該類。
今天主要分析SpringBoot初始化實例的源碼分析,本章暫時分析到:
下一章節再繼續分析run方法。