Spring中Bean的加載與SpringBoot的初始化流程詳解
前言
一直對(duì)它們之間的關(guān)系感到好奇,SpringBoot既然是Spring的封裝,那么SpringBoot在初始化時(shí)應(yīng)該也會(huì)有Bean的加載,那么是在何時(shí)進(jìn)行加載的呢?
第一章 Spring中Bean的一些簡(jiǎn)單概念
1.1 SpingIOC簡(jiǎn)介
Spring啟動(dòng)時(shí)去讀取應(yīng)用程序提供的Bean配置信息,并在Spring容器中生成相應(yīng)的Bean定義注冊(cè)表,然后根據(jù)注冊(cè)表去實(shí)例化Bean,裝配好Bean之間的依賴(lài)關(guān)系,為上層提供準(zhǔn)備就緒的運(yùn)行環(huán)境.
Spring提供一個(gè)配置文件描述Bean與Bean之間的依賴(lài)關(guān)系,利用Java語(yǔ)言的反射功能實(shí)例化Bean,并建立Bean之間的依賴(lài)關(guān)系.
1.2 BeanFactory
BeanFactory是接口,提供了IOC容器最基本的形式,給具體的IOC容器的實(shí)現(xiàn)提供了規(guī)范。
1.2.1 BeanDefinition
主要用來(lái)描述Bean的定義,Spring在啟動(dòng)時(shí)會(huì)將Xml或者注解里Bean的定義解析成Spring內(nèi)部的BeanDefinition.
beanClass保存bean的class屬性,scop保存bean是否單例,abstractFlag保存該bean是否抽象,lazyInit保存是否延遲初始化,autowireMode保存是否自動(dòng)裝配,等等等
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable { private volatile Object beanClass; private String scope = SCOPE_DEFAULT; private boolean abstractFlag = false; private boolean lazyInit = false; private int autowireMode = AUTOWIRE_NO; private int dependencyCheck = DEPENDENCY_CHECK_NONE; private String[] dependsOn; private ConstructorArgumentValues constructorArgumentValues; private MutablePropertyValues propertyValues; private String factoryBeanName; private String factoryMethodName; private String initMethodName; private String destroyMethodName; }
1.2.2 BeanDefinitionRegistry
registerBeanDefinition方法主要是將BeanDefinition注冊(cè)到BeanFactory接口的實(shí)現(xiàn)類(lèi)DefaultListableBeanFacory中的beanDefinitionMap中。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
1.2.3 BeanFactory結(jié)構(gòu)圖
ListableBeanFactory該接口定義了訪問(wèn)容器中Bean的若干方法,如查看Bean的個(gè)數(shù),獲取某一類(lèi)型Bean的配置名,查看容器中是否包括某一Bean等方法.
HierarchicalBeanFactory是父子級(jí)聯(lián)的IOC容器接口,子容器可以通過(guò)接口方法訪問(wèn)父容器,通過(guò)HierarchicalBeanFactory接口SpringIOC可以建立父子層級(jí)關(guān)聯(lián)的IOC層級(jí)體系,子容器可以訪問(wèn)父容器的Bean,父容器不能訪問(wèn)子容器的Bean,比如展現(xiàn)層的Bean位于子容器中而業(yè)務(wù)層和持久層的Bean位于父容器的Bean.
ConfigurableBeanFactory
:增強(qiáng)了IOC接口的可定制性,定義了設(shè)置類(lèi)裝載器,屬性遍歷器,以及屬性初始化后置處理器等方法.AutowireCapableBeanFactory
:定義了將容器中的Bean按某種規(guī)則,按名字匹配,按類(lèi)型匹配等.SingletonBeanRegistry
:允許在運(yùn)行期間向容器注冊(cè)SingletonBean實(shí)例的方法.
通過(guò)這些接口也證明了BeanFactory的體系也確實(shí)提供了IOC的基礎(chǔ)及依賴(lài)注入和Bean的裝載等功能.
1.3 ApplicationContext
由于BeanFactory的功能還不夠強(qiáng)大,于是Spring在BeanFactory的基礎(chǔ)上還設(shè)計(jì)了一個(gè)更為高級(jí)的接口即ApplicationContext,它是BeanFactory的子接口之一.在我們使用SpringIOC容器時(shí),大部分都是context的實(shí)現(xiàn)類(lèi)。
我理解著就是BeanFactory只提供IOC,ApplicationContext還提供很多別的功能。
第二章 SpringBoot的初始化流程
@SpringBootApplication public class RepApplication { public static void main(String[] args) { //要理解的SpringApplication SpringApplication.run(RepApplication.class, args); } }
SpringApplication的run分為兩個(gè)階段,即new SpringApplication()時(shí)的執(zhí)行構(gòu)造函數(shù)的準(zhǔn)備階段,和run時(shí)的運(yùn)行階段。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
2.1 準(zhǔn)備階段
在準(zhǔn)備階段會(huì)
配置SpringBean的來(lái)源
推斷web應(yīng)用類(lèi)型
加載應(yīng)用上下文初始器
加載應(yīng)用事件監(jiān)聽(tīng)器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //推斷web應(yīng)用類(lèi)型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //加載應(yīng)用上下文初始化器 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //加載應(yīng)用事件監(jiān)聽(tīng)器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
2.2 運(yùn)行階段
- 加載:SpringApplication獲得監(jiān)聽(tīng)器
- 運(yùn)行:SpringApplication運(yùn)行監(jiān)聽(tīng)器
- 監(jiān)聽(tīng):SpringBoot事件、Spring事件
- 創(chuàng)建:應(yīng)用上下文、Enviroment、其它(不重要),應(yīng)用上下文創(chuàng)建后會(huì)被應(yīng)用上下文初始化器初始化,Enviroment是抽象的環(huán)境對(duì)象。
- 失?。汗收戏治鰣?bào)告。
- 回調(diào):CommandLineRunner、ApplicationRunner
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //獲得監(jiān)聽(tīng)器 SpringApplicationRunListeners listeners = getRunListeners(args); //運(yùn)行監(jiān)聽(tīng)器 listeners.starting(); try { //應(yīng)用上下文 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //環(huán)境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); //依據(jù)不同的配置加載不同的ApplicationContext context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
2.2.1 監(jiān)聽(tīng)器分析
這個(gè)是看了源碼后的個(gè)人理解,不保證一定正確,只提供一定的參考。
在準(zhǔn)備階段加載實(shí)現(xiàn)了ApplicationListener的監(jiān)聽(tīng)器。
然后在運(yùn)行階段調(diào)用了starting()方法。
//獲得監(jiān)聽(tīng)器 SpringApplicationRunListeners listeners = getRunListeners(args); //運(yùn)行監(jiān)聽(tīng)器 listeners.starting();
下面是listeners的源碼,可以看到它的每一個(gè)方法,都對(duì)應(yīng)SpringBoot的一個(gè)階段,這表明每到對(duì)應(yīng)的階段,都要廣播對(duì)應(yīng)的事件。
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } public void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } public void contextPrepared(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextPrepared(context); } } public void contextLoaded(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextLoaded(context); } } public void started(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.started(context); } } //省略... }
那么問(wèn)題來(lái)了,廣播事件后,事件是怎么被監(jiān)聽(tīng)到的呢?我們打開(kāi)listener.environmentPrepared(environment)的源碼,發(fā)現(xiàn)其調(diào)用了initialMulticaster進(jìn)行了事件廣播
@Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); }
廣播的代碼如下,看了一下感覺(jué)大意就是找到根事件匹配的監(jiān)聽(tīng)器,然后調(diào)用線程池去執(zhí)行對(duì)應(yīng)的觸發(fā)函數(shù)。
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
2.2.2 refreshContext
再往下最核心的是refreshContext方法,一直點(diǎn)進(jìn)去可以看到如下:
prepareRefresh
:完成配置之類(lèi)的解析,設(shè)置Spring的狀態(tài),初始化屬性源信息,驗(yàn)證環(huán)境信息中必須存在的屬性.ConfigurableListableBeanFactory
:是用來(lái)獲取beanFactory的實(shí)例的(第一張也寫(xiě)過(guò)BeanFactory負(fù)責(zé)bean的加載與獲?。?。PrepareBeanFactory
:對(duì)beanFactory進(jìn)行相關(guān)的設(shè)置,為后續(xù)的使用做準(zhǔn)備,包括設(shè)置classLoader用來(lái)加載Bean,設(shè)置表達(dá)式解析器等等.postProcessBeanFactory
:是用于在BeanFactory設(shè)置之后進(jìn)行后續(xù)的BeanFactory的操作.invokeBeanFactoryPostProcessors
:點(diǎn)進(jìn)去發(fā)現(xiàn)調(diào)用了如下方法,點(diǎn)進(jìn)去之后發(fā)現(xiàn)邏輯相當(dāng)復(fù)雜,主要調(diào)用工廠后處理器,調(diào)用Bean標(biāo)簽,掃描Bean文件,并解析成一個(gè)個(gè)的Bean,這時(shí)候這些Bean是被加載進(jìn)了Spirng容器當(dāng)中,這里涉及了各種類(lèi),我們?cè)谶@里主要說(shuō)一下ConfigurationClassParser,主要是解析Bean的類(lèi).該方法會(huì)對(duì)帶有@configuration,@import,@bean,以及@SpringBootApplication等標(biāo)簽的Bean進(jìn)行解析,registerBeanPostProcessors
:會(huì)從Spring容器中找出實(shí)現(xiàn)BeanPostProcessors接口的Bean,并設(shè)置到BeanFactory的屬性之中,之后Bean實(shí)例化時(shí)會(huì)調(diào)用BeanProcessor,也就是Bean的后置處理器.會(huì)和AOP比較相關(guān).initMessageSource
:初始化消息源(這個(gè)自己推斷的)initApplicationEventMuticaster
:初始化事件廣播器onRefresh
:是一個(gè)模板方法,不同的Spring容器會(huì)重寫(xiě)它做不同的事情.比如web程序的容器,會(huì)調(diào)用create..方法去創(chuàng)建內(nèi)置的servlet容器.registerListeners
:注冊(cè)事件監(jiān)聽(tīng)器finishBeanFactoryInitialization
:會(huì)實(shí)例化BeanFactory中已被注冊(cè)但未被實(shí)例化的所有實(shí)例,懶加載是不需要被實(shí)例化的.前面的invokeBeanFactoryPostProcessors方法中根據(jù)各種注解解析出來(lái)的Bean在這個(gè)時(shí)候都會(huì)被初始化,同時(shí)初始化過(guò)程中的各種PostProcessor就會(huì)開(kāi)始起作用了.finishRefresh
:會(huì)做初始化生命周期處理器相關(guān)的事情.
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
當(dāng)上面的代碼執(zhí)行完畢后,返回上層代碼,后面是注冊(cè)鉤子,這鉤子是希望開(kāi)發(fā)者能結(jié)合自己的實(shí)際需求擴(kuò)展出一些在Spring容器關(guān)閉時(shí)的行為.
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
繼續(xù)返回上層代碼,可以看到afterRefresh方法它的方法體是空的, 也就說(shuō)明Spring框架考慮了擴(kuò)展性,留了很多的口子,讓大家在框架層面繼承很多的模塊并去做自定義的實(shí)現(xiàn)
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
2.3 總結(jié)
總的來(lái)說(shuō),SpringBoot加載的Bean的時(shí)機(jī)為,點(diǎn)進(jìn)一開(kāi)始的run方法,層層遞進(jìn)后,由
refreshContext(context);
進(jìn)行了bean的加載,更詳細(xì)的話,那就層層遞進(jìn)點(diǎn)進(jìn)去,是在如下方法進(jìn)行了bean的加載。
invokeBeanFactoryPostProcessors(beanFactory);
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring?代碼技巧梳理總結(jié)讓你愛(ài)不釋手
這篇文章主要分享了Spring?代碼技巧梳理總結(jié),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06JAVA線上常見(jiàn)問(wèn)題排查手段(小結(jié))
這篇文章主要介紹了JAVA線上常見(jiàn)問(wèn)題排查手段(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-07-07通過(guò)代碼實(shí)例解析JAVA類(lèi)生命周期
這篇文章主要介紹了通過(guò)代碼實(shí)例解析JAVA類(lèi)生命周期,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08springboot中項(xiàng)目啟動(dòng)時(shí)實(shí)現(xiàn)初始化方法加載參數(shù)
這篇文章主要介紹了springboot中項(xiàng)目啟動(dòng)時(shí)實(shí)現(xiàn)初始化方法加載參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java實(shí)現(xiàn)控制小數(shù)精度的方法
這篇文章主要介紹了Java實(shí)現(xiàn)控制小數(shù)精度的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java線程安全的計(jì)數(shù)器簡(jiǎn)單實(shí)現(xiàn)代碼示例
這篇文章主要介紹了Java線程安全的計(jì)數(shù)器簡(jiǎn)單實(shí)現(xiàn)代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決
這篇文章主要介紹了RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Springboot Redis設(shè)置key前綴的方法步驟
這篇文章主要介紹了Springboot Redis設(shè)置key前綴的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04