摘要:設(shè)置應(yīng)用上線文初始化器的作用是什么源碼如下。來看下方法源碼,其實就是初始化一個應(yīng)用上下文初始化器實例的集合。設(shè)置監(jiān)聽器和設(shè)置初始化器調(diào)用的方法是一樣的,只是傳入的類型不一樣,設(shè)置監(jiān)聽器的接口類型為,對應(yīng)的文件配置內(nèi)容請見下方。
Spring Boot 的應(yīng)用教程我們已經(jīng)分享過很多了,今天來通過源碼來分析下它的啟動過程,探究下 Spring Boot 為什么這么簡便的奧秘。
本篇基于 Spring Boot 2.0.3 版本進行分析,閱讀本文需要有一些 Java 和 Spring 框架基礎(chǔ),如果還不知道 Spring Boot 是什么,建議先看下我們的 Spring Boot 教程。
Spring Boot 的入口類@SpringBootApplication public class SpringBootBestPracticeApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBestPracticeApplication.class, args); } }
做過 Spring Boot 項目的都知道,上面是 Spring Boot 最簡單通用的入口類。入口類的要求是最頂層包下面第一個含有 main 方法的類,使用注解 @SpringBootApplication 來啟用 Spring Boot 特性,使用 SpringApplication.run 方法來啟動 Spring Boot 項目。
來看一下這個類的 run 方法調(diào)用關(guān)系源碼:
public static ConfigurableApplicationContext run(Class> primarySource, String... args) { return run(new Class>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
第一個參數(shù) primarySource:加載的主要資源類
第二個參數(shù) args:傳遞給應(yīng)用的應(yīng)用參數(shù)
先用主要資源類來實例化一個 SpringApplication 對象,再調(diào)用這個對象的 run 方法,所以我們分兩步來分析這個啟動源碼。
SpringApplication 的實例化過程接著上面的 SpringApplication 構(gòu)造方法進入以下源碼:
public SpringApplication(Class>... primarySources) { this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { // 1、資源初始化資源加載器為 null this.resourceLoader = resourceLoader; // 2、斷言主要加載資源類不能為 null,否則報錯 Assert.notNull(primarySources, "PrimarySources must not be null"); // 3、初始化主要加載資源類集合并去重 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 4、推斷當(dāng)前 WEB 應(yīng)用類型 this.webApplicationType = deduceWebApplicationType(); // 5、設(shè)置應(yīng)用上線文初始化器 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); // 6、設(shè)置監(jiān)聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 7、推斷主入口應(yīng)用類 this.mainApplicationClass = deduceMainApplicationClass(); }
可知這個構(gòu)造器類的初始化包括以下 7 個過程。
1、資源初始化資源加載器為 nullthis.resourceLoader = resourceLoader;2、斷言主要加載資源類不能為 null,否則報錯
Assert.notNull(primarySources, "PrimarySources must not be null");3、初始化主要加載資源類集合并去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));4、推斷當(dāng)前 WEB 應(yīng)用類型
this.webApplicationType = deduceWebApplicationType();
來看下 deduceWebApplicationType 方法和相關(guān)的源碼:
private WebApplicationType deduceWebApplicationType() { if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; } private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; public enum WebApplicationType { /** * 非 WEB 項目 */ NONE, /** * SERVLET WEB 項目 */ SERVLET, /** * 響應(yīng)式 WEB 項目 */ REACTIVE }
這個就是根據(jù)類路徑下是否有對應(yīng)項目類型的類推斷出不同的應(yīng)用類型。
5、設(shè)置應(yīng)用上線文初始化器setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
ApplicationContextInitializer 的作用是什么?源碼如下。
public interface ApplicationContextInitializer{ /** * Initialize the given application context. * @param applicationContext the application to configure */ void initialize(C applicationContext); }
用來初始化指定的 Spring 應(yīng)用上下文,如注冊屬性資源、激活 Profiles 等。
來看下 setInitializers 方法源碼,其實就是初始化一個 ApplicationContextInitializer 應(yīng)用上下文初始化器實例的集合。
public void setInitializers( Collection extends ApplicationContextInitializer>> initializers) { this.initializers = new ArrayList<>(); this.initializers.addAll(initializers); }
再來看下這個初始化 getSpringFactoriesInstances 方法和相關(guān)的源碼:
privateCollection getSpringFactoriesInstances(Class type) { return getSpringFactoriesInstances(type, new Class>[] {}); } private Collection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
設(shè)置應(yīng)用上下文初始化器可分為以下 5 個步驟。
5.1)獲取當(dāng)前線程上下文類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
5.2)獲取 ApplicationContextInitializer 的實例名稱集合并去重
Setnames = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));
loadFactoryNames 方法相關(guān)的源碼如下:
public static ListloadFactoryNames(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 > loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration 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()) { List factoryClassNames = Arrays.asList( StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); result.addAll((String) entry.getKey(), factoryClassNames); } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
根據(jù)類路徑下的 META-INF/spring.factories 文件解析并獲取 ApplicationContextInitializer 接口的所有配置的類路徑名稱。
spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 的初始化器相關(guān)配置內(nèi)容如下:
# Initializers org.springframework.context.ApplicationContextInitializer= org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer, org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
5.3)根據(jù)以上類路徑創(chuàng)建初始化器實例列表
Listinstances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); private List createSpringFactoriesInstances(Class type, Class>[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) { List 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; }
5.4)初始化器實例列表排序
AnnotationAwareOrderComparator.sort(instances);
5.5)返回初始化器實例列表
return instances;6、設(shè)置監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
ApplicationListener 的作用是什么?源碼如下。
@FunctionalInterface public interface ApplicationListenerextends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
看源碼,這個接口繼承了 JDK 的 java.util.EventListener 接口,實現(xiàn)了觀察者模式,它一般用來定義感興趣的事件類型,事件類型限定于 ApplicationEvent 的子類,這同樣繼承了 JDK 的 java.util.EventObject 接口。
設(shè)置監(jiān)聽器和設(shè)置初始化器調(diào)用的方法是一樣的,只是傳入的類型不一樣,設(shè)置監(jiān)聽器的接口類型為:getSpringFactoriesInstances,對應(yīng)的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置內(nèi)容請見下方。
# Application Listeners org.springframework.context.ApplicationListener= org.springframework.boot.autoconfigure.BackgroundPreinitializer
可以看出目前只有一個 BackgroundPreinitializer 監(jiān)聽器。
7、推斷主入口應(yīng)用類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; }
這個推斷入口應(yīng)用類的方式有點特別,通過構(gòu)造一個運行時異常,再遍歷異常棧中的方法名,獲取方法名為 main 的棧幀,從來得到入口類的名字再返回該類。
總結(jié)源碼分析內(nèi)容有點多,也很麻煩,本章暫時分析到 SpringApplication 構(gòu)造方法的初始化流程,下章再繼續(xù)分析其 run 方法,作者很快寫完過兩天就發(fā)布,掃碼關(guān)注下面的公眾號 "Java技術(shù)棧" 即可獲取推送更新。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/76638.html
摘要:參考創(chuàng)建所有運行監(jiān)聽器并發(fā)布應(yīng)用啟動事件來看下創(chuàng)建運行監(jiān)聽器相關(guān)的源碼創(chuàng)建邏輯和之前實例化初始化器和監(jiān)聽器的一樣,一樣調(diào)用的是方法來獲取配置的監(jiān)聽器名稱并實例化所有的類。 上篇《Spring Boot 2.x 啟動全過程源碼分析(一)入口類剖析》我們分析了 Spring Boot 入口類 SpringApplication 的源碼,并知道了其構(gòu)造原理,這篇我們繼續(xù)往下面分析其核心 ru...
摘要:引入了新的環(huán)境和概要信息,是一種更揭秘與實戰(zhàn)六消息隊列篇掘金本文,講解如何集成,實現(xiàn)消息隊列。博客地址揭秘與實戰(zhàn)二數(shù)據(jù)緩存篇掘金本文,講解如何集成,實現(xiàn)緩存。 Spring Boot 揭秘與實戰(zhàn)(九) 應(yīng)用監(jiān)控篇 - HTTP 健康監(jiān)控 - 掘金Health 信息是從 ApplicationContext 中所有的 HealthIndicator 的 Bean 中收集的, Spring...
摘要:用于主類上最最最核心的注解,表示這是一個項目,用于開啟的各項能力。下面我們來分析一下這個注解的組成以及作用通過上面的代碼我們可以看出來是一個組合注解,主要由和這三個注解組成的。通過源碼可以看出也是一個組合注解。 ??SpringBoot項目一般都會有Application的入口類,入口類中會有main方法,這是一個標準的java應(yīng)用程序的入口方法。@SpringBootApplicat...
摘要:核心注解講解最大的特點是無需配置文件,能自動掃描包路徑裝載并注入對象,并能做到根據(jù)下的包自動配置。所以最核心的個注解就是這是添加的一個注解,用來代替配置文件,所有這個配置文件里面能做到的事情都可以通過這個注解所在類來進行注冊。 最近面試一些 Java 開發(fā)者,他們其中有些在公司實際用過 Spring Boot, 有些是自己興趣愛好在業(yè)余自己學(xué)習(xí)過。然而,當(dāng)我問他們 Spring Boo...
摘要:我又問微服務(wù)和有什么關(guān)系不用行不行然后對方就吱吱唔唔了可以打包部署,內(nèi)部集成了。為什么說是自動配置的開啟注解是,其實它就是由下面三個注解組成的上面三個注解,前面兩個都是自帶的,和無關(guān),所以說上面的回答的不是在點上。 最近棧長面試了不少人,其中不乏說對 Spring Boot 非常熟悉的,然后當(dāng)我問到一些 Spring Boot 核心功能和原理的時候,沒人能說得上來,或者說不到點上,可以...
閱讀 1587·2019-08-30 13:18
閱讀 1576·2019-08-29 12:19
閱讀 2094·2019-08-26 13:57
閱讀 4137·2019-08-26 13:22
閱讀 1179·2019-08-26 10:35
閱讀 2990·2019-08-23 18:09
閱讀 2500·2019-08-23 17:19
閱讀 675·2019-08-23 17:18