摘要:設(shè)置應(yīng)用上線文初始化器的作用是什么源碼如下。來(lái)看下方法源碼,其實(shí)就是初始化一個(gè)應(yīng)用上下文初始化器實(shí)例的集合。設(shè)置監(jiān)聽(tīng)器和設(shè)置初始化器調(diào)用的方法是一樣的,只是傳入的類型不一樣,設(shè)置監(jiān)聽(tīng)器的接口類型為,對(duì)應(yīng)的文件配置內(nèi)容請(qǐng)見(jiàn)下方。
Spring Boot 的應(yīng)用教程我們已經(jīng)分享過(guò)很多了,今天來(lái)通過(guò)源碼來(lái)分析下它的啟動(dòng)過(guò)程,探究下 Spring Boot 為什么這么簡(jiǎn)便的奧秘。
本篇基于 Spring Boot 2.0.3 版本進(jìn)行分析,閱讀本文需要有一些 Java 和 Spring 框架基礎(chǔ),如果還不知道 Spring Boot 是什么,建議先看下我們的 Spring Boot 教程。
Spring Boot 的入口類@SpringBootApplication public class SpringBootBestPracticeApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBestPracticeApplication.class, args); } }
做過(guò) Spring Boot 項(xiàng)目的都知道,上面是 Spring Boot 最簡(jiǎn)單通用的入口類。入口類的要求是最頂層包下面第一個(gè)含有 main 方法的類,使用注解 @SpringBootApplication 來(lái)啟用 Spring Boot 特性,使用 SpringApplication.run 方法來(lái)啟動(dòng) Spring Boot 項(xiàng)目。
來(lái)看一下這個(gè)類的 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); }
第一個(gè)參數(shù) primarySource:加載的主要資源類
第二個(gè)參數(shù) args:傳遞給應(yīng)用的應(yīng)用參數(shù)
先用主要資源類來(lái)實(shí)例化一個(gè) SpringApplication 對(duì)象,再調(diào)用這個(gè)對(duì)象的 run 方法,所以我們分兩步來(lái)分析這個(gè)啟動(dòng)源碼。
SpringApplication 的實(shí)例化過(guò)程接著上面的 SpringApplication 構(gòu)造方法進(jìn)入以下源碼:
public SpringApplication(Class>... primarySources) { this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { // 1、資源初始化資源加載器為 null this.resourceLoader = resourceLoader; // 2、斷言主要加載資源類不能為 null,否則報(bào)錯(cuò) 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)聽(tīng)器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 7、推斷主入口應(yīng)用類 this.mainApplicationClass = deduceMainApplicationClass(); }
可知這個(gè)構(gòu)造器類的初始化包括以下 7 個(gè)過(guò)程。
1、資源初始化資源加載器為 nullthis.resourceLoader = resourceLoader;2、斷言主要加載資源類不能為 null,否則報(bào)錯(cuò)
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();
來(lái)看下 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 項(xiàng)目 */ NONE, /** * SERVLET WEB 項(xiàng)目 */ SERVLET, /** * 響應(yīng)式 WEB 項(xiàng)目 */ REACTIVE }
這個(gè)就是根據(jù)類路徑下是否有對(duì)應(yīng)項(xià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); }
用來(lái)初始化指定的 Spring 應(yīng)用上下文,如注冊(cè)屬性資源、激活 Profiles 等。
來(lái)看下 setInitializers 方法源碼,其實(shí)就是初始化一個(gè) ApplicationContextInitializer 應(yīng)用上下文初始化器實(shí)例的集合。
public void setInitializers( Collection extends ApplicationContextInitializer>> initializers) { this.initializers = new ArrayList<>(); this.initializers.addAll(initializers); }
再來(lái)看下這個(gè)初始化 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 個(gè)步驟。
5.1)獲取當(dāng)前線程上下文類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
5.2)獲取 ApplicationContextInitializer 的實(shí)例名稱集合并去重
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)建初始化器實(shí)例列表
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)初始化器實(shí)例列表排序
AnnotationAwareOrderComparator.sort(instances);
5.5)返回初始化器實(shí)例列表
return instances;6、設(shè)置監(jiān)聽(tīng)器
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); }
看源碼,這個(gè)接口繼承了 JDK 的 java.util.EventListener 接口,實(shí)現(xiàn)了觀察者模式,它一般用來(lái)定義感興趣的事件類型,事件類型限定于 ApplicationEvent 的子類,這同樣繼承了 JDK 的 java.util.EventObject 接口。
設(shè)置監(jiān)聽(tīng)器和設(shè)置初始化器調(diào)用的方法是一樣的,只是傳入的類型不一樣,設(shè)置監(jiān)聽(tīng)器的接口類型為:getSpringFactoriesInstances,對(duì)應(yīng)的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置內(nèi)容請(qǐng)見(jiàn)下方。
# Application Listeners org.springframework.context.ApplicationListener= org.springframework.boot.autoconfigure.BackgroundPreinitializer
可以看出目前只有一個(gè) BackgroundPreinitializer 監(jiān)聽(tīng)器。
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; }
這個(gè)推斷入口應(yīng)用類的方式有點(diǎn)特別,通過(guò)構(gòu)造一個(gè)運(yùn)行時(shí)異常,再遍歷異常棧中的方法名,獲取方法名為 main 的棧幀,從來(lái)得到入口類的名字再返回該類。
總結(jié)源碼分析內(nèi)容有點(diǎn)多,也很麻煩,本章暫時(shí)分析到 SpringApplication 構(gòu)造方法的初始化流程,下章再繼續(xù)分析其 run 方法,作者很快寫完過(guò)兩天就發(fā)布,掃碼關(guān)注下面的公眾號(hào) "Java技術(shù)棧" 即可獲取推送更新。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/76638.html
摘要:參考創(chuàng)建所有運(yùn)行監(jiān)聽(tīng)器并發(fā)布應(yīng)用啟動(dòng)事件來(lái)看下創(chuàng)建運(yùn)行監(jiān)聽(tīng)器相關(guān)的源碼創(chuàng)建邏輯和之前實(shí)例化初始化器和監(jiān)聽(tīng)器的一樣,一樣調(diào)用的是方法來(lái)獲取配置的監(jiān)聽(tīng)器名稱并實(shí)例化所有的類。 上篇《Spring Boot 2.x 啟動(dòng)全過(guò)程源碼分析(一)入口類剖析》我們分析了 Spring Boot 入口類 SpringApplication 的源碼,并知道了其構(gòu)造原理,這篇我們繼續(xù)往下面分析其核心 ru...
摘要:引入了新的環(huán)境和概要信息,是一種更揭秘與實(shí)戰(zhàn)六消息隊(duì)列篇掘金本文,講解如何集成,實(shí)現(xiàn)消息隊(duì)列。博客地址揭秘與實(shí)戰(zhàn)二數(shù)據(jù)緩存篇掘金本文,講解如何集成,實(shí)現(xiàn)緩存。 Spring Boot 揭秘與實(shí)戰(zhàn)(九) 應(yīng)用監(jiān)控篇 - HTTP 健康監(jiān)控 - 掘金Health 信息是從 ApplicationContext 中所有的 HealthIndicator 的 Bean 中收集的, Spring...
摘要:用于主類上最最最核心的注解,表示這是一個(gè)項(xiàng)目,用于開啟的各項(xiàng)能力。下面我們來(lái)分析一下這個(gè)注解的組成以及作用通過(guò)上面的代碼我們可以看出來(lái)是一個(gè)組合注解,主要由和這三個(gè)注解組成的。通過(guò)源碼可以看出也是一個(gè)組合注解。 ??SpringBoot項(xiàng)目一般都會(huì)有Application的入口類,入口類中會(huì)有main方法,這是一個(gè)標(biāo)準(zhǔn)的java應(yīng)用程序的入口方法。@SpringBootApplicat...
摘要:核心注解講解最大的特點(diǎn)是無(wú)需配置文件,能自動(dòng)掃描包路徑裝載并注入對(duì)象,并能做到根據(jù)下的包自動(dòng)配置。所以最核心的個(gè)注解就是這是添加的一個(gè)注解,用來(lái)代替配置文件,所有這個(gè)配置文件里面能做到的事情都可以通過(guò)這個(gè)注解所在類來(lái)進(jìn)行注冊(cè)。 最近面試一些 Java 開發(fā)者,他們其中有些在公司實(shí)際用過(guò) Spring Boot, 有些是自己興趣愛(ài)好在業(yè)余自己學(xué)習(xí)過(guò)。然而,當(dāng)我問(wèn)他們 Spring Boo...
摘要:我又問(wèn)微服務(wù)和有什么關(guān)系不用行不行然后對(duì)方就吱吱唔唔了可以打包部署,內(nèi)部集成了。為什么說(shuō)是自動(dòng)配置的開啟注解是,其實(shí)它就是由下面三個(gè)注解組成的上面三個(gè)注解,前面兩個(gè)都是自帶的,和無(wú)關(guān),所以說(shuō)上面的回答的不是在點(diǎn)上。 最近棧長(zhǎng)面試了不少人,其中不乏說(shuō)對(duì) Spring Boot 非常熟悉的,然后當(dāng)我問(wèn)到一些 Spring Boot 核心功能和原理的時(shí)候,沒(méi)人能說(shuō)得上來(lái),或者說(shuō)不到點(diǎn)上,可以...
閱讀 1597·2019-08-30 13:18
閱讀 1583·2019-08-29 12:19
閱讀 2126·2019-08-26 13:57
閱讀 4150·2019-08-26 13:22
閱讀 1190·2019-08-26 10:35
閱讀 2996·2019-08-23 18:09
閱讀 2510·2019-08-23 17:19
閱讀 686·2019-08-23 17:18