Spring中Bean的加載與SpringBoot的初始化流程詳解
前言
一直對它們之間的關系感到好奇,SpringBoot既然是Spring的封裝,那么SpringBoot在初始化時應該也會有Bean的加載,那么是在何時進行加載的呢?
第一章 Spring中Bean的一些簡單概念
1.1 SpingIOC簡介
Spring啟動時去讀取應用程序提供的Bean配置信息,并在Spring容器中生成相應的Bean定義注冊表,然后根據(jù)注冊表去實例化Bean,裝配好Bean之間的依賴關系,為上層提供準備就緒的運行環(huán)境.
Spring提供一個配置文件描述Bean與Bean之間的依賴關系,利用Java語言的反射功能實例化Bean,并建立Bean之間的依賴關系.

1.2 BeanFactory
BeanFactory是接口,提供了IOC容器最基本的形式,給具體的IOC容器的實現(xiàn)提供了規(guī)范。
1.2.1 BeanDefinition
主要用來描述Bean的定義,Spring在啟動時會將Xml或者注解里Bean的定義解析成Spring內部的BeanDefinition.
beanClass保存bean的class屬性,scop保存bean是否單例,abstractFlag保存該bean是否抽象,lazyInit保存是否延遲初始化,autowireMode保存是否自動裝配,等等等
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注冊到BeanFactory接口的實現(xiàn)類DefaultListableBeanFacory中的beanDefinitionMap中。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
1.2.3 BeanFactory結構圖
ListableBeanFactory該接口定義了訪問容器中Bean的若干方法,如查看Bean的個數(shù),獲取某一類型Bean的配置名,查看容器中是否包括某一Bean等方法.
HierarchicalBeanFactory是父子級聯(lián)的IOC容器接口,子容器可以通過接口方法訪問父容器,通過HierarchicalBeanFactory接口SpringIOC可以建立父子層級關聯(lián)的IOC層級體系,子容器可以訪問父容器的Bean,父容器不能訪問子容器的Bean,比如展現(xiàn)層的Bean位于子容器中而業(yè)務層和持久層的Bean位于父容器的Bean.
ConfigurableBeanFactory:增強了IOC接口的可定制性,定義了設置類裝載器,屬性遍歷器,以及屬性初始化后置處理器等方法.AutowireCapableBeanFactory:定義了將容器中的Bean按某種規(guī)則,按名字匹配,按類型匹配等.SingletonBeanRegistry:允許在運行期間向容器注冊SingletonBean實例的方法.
通過這些接口也證明了BeanFactory的體系也確實提供了IOC的基礎及依賴注入和Bean的裝載等功能.

1.3 ApplicationContext
由于BeanFactory的功能還不夠強大,于是Spring在BeanFactory的基礎上還設計了一個更為高級的接口即ApplicationContext,它是BeanFactory的子接口之一.在我們使用SpringIOC容器時,大部分都是context的實現(xiàn)類。

我理解著就是BeanFactory只提供IOC,ApplicationContext還提供很多別的功能。

第二章 SpringBoot的初始化流程
@SpringBootApplication
public class RepApplication {
public static void main(String[] args) {
//要理解的SpringApplication
SpringApplication.run(RepApplication.class, args);
}
}
SpringApplication的run分為兩個階段,即new SpringApplication()時的執(zhí)行構造函數(shù)的準備階段,和run時的運行階段。
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
2.1 準備階段
在準備階段會
配置SpringBean的來源
推斷web應用類型
加載應用上下文初始器
加載應用事件監(jiān)聽器
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應用類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//加載應用上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//加載應用事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.2 運行階段
- 加載:SpringApplication獲得監(jiān)聽器
- 運行:SpringApplication運行監(jiān)聽器
- 監(jiān)聽:SpringBoot事件、Spring事件
- 創(chuàng)建:應用上下文、Enviroment、其它(不重要),應用上下文創(chuàng)建后會被應用上下文初始化器初始化,Enviroment是抽象的環(huán)境對象。
- 失?。汗收戏治鰣蟾妗?/li>
- 回調:CommandLineRunner、ApplicationRunner
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//獲得監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
//運行監(jiān)聽器
listeners.starting();
try {
//應用上下文
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)聽器分析
這個是看了源碼后的個人理解,不保證一定正確,只提供一定的參考。
在準備階段加載實現(xiàn)了ApplicationListener的監(jiān)聽器。

然后在運行階段調用了starting()方法。
//獲得監(jiān)聽器 SpringApplicationRunListeners listeners = getRunListeners(args); //運行監(jiān)聽器 listeners.starting();
下面是listeners的源碼,可以看到它的每一個方法,都對應SpringBoot的一個階段,這表明每到對應的階段,都要廣播對應的事件。

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);
}
}
//省略...
}
那么問題來了,廣播事件后,事件是怎么被監(jiān)聽到的呢?我們打開listener.environmentPrepared(environment)的源碼,發(fā)現(xiàn)其調用了initialMulticaster進行了事件廣播
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
廣播的代碼如下,看了一下感覺大意就是找到根事件匹配的監(jiān)聽器,然后調用線程池去執(zhí)行對應的觸發(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方法,一直點進去可以看到如下:
prepareRefresh:完成配置之類的解析,設置Spring的狀態(tài),初始化屬性源信息,驗證環(huán)境信息中必須存在的屬性.ConfigurableListableBeanFactory:是用來獲取beanFactory的實例的(第一張也寫過BeanFactory負責bean的加載與獲?。?。PrepareBeanFactory:對beanFactory進行相關的設置,為后續(xù)的使用做準備,包括設置classLoader用來加載Bean,設置表達式解析器等等.postProcessBeanFactory:是用于在BeanFactory設置之后進行后續(xù)的BeanFactory的操作.invokeBeanFactoryPostProcessors:點進去發(fā)現(xiàn)調用了如下方法,點進去之后發(fā)現(xiàn)邏輯相當復雜,主要調用工廠后處理器,調用Bean標簽,掃描Bean文件,并解析成一個個的Bean,這時候這些Bean是被加載進了Spirng容器當中,這里涉及了各種類,我們在這里主要說一下ConfigurationClassParser,主要是解析Bean的類.該方法會對帶有@configuration,@import,@bean,以及@SpringBootApplication等標簽的Bean進行解析,registerBeanPostProcessors:會從Spring容器中找出實現(xiàn)BeanPostProcessors接口的Bean,并設置到BeanFactory的屬性之中,之后Bean實例化時會調用BeanProcessor,也就是Bean的后置處理器.會和AOP比較相關.initMessageSource:初始化消息源(這個自己推斷的)initApplicationEventMuticaster:初始化事件廣播器onRefresh:是一個模板方法,不同的Spring容器會重寫它做不同的事情.比如web程序的容器,會調用create..方法去創(chuàng)建內置的servlet容器.registerListeners:注冊事件監(jiān)聽器finishBeanFactoryInitialization:會實例化BeanFactory中已被注冊但未被實例化的所有實例,懶加載是不需要被實例化的.前面的invokeBeanFactoryPostProcessors方法中根據(jù)各種注解解析出來的Bean在這個時候都會被初始化,同時初始化過程中的各種PostProcessor就會開始起作用了.finishRefresh:會做初始化生命周期處理器相關的事情.
@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();
}
}
}
當上面的代碼執(zhí)行完畢后,返回上層代碼,后面是注冊鉤子,這鉤子是希望開發(fā)者能結合自己的實際需求擴展出一些在Spring容器關閉時的行為.
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
繼續(xù)返回上層代碼,可以看到afterRefresh方法它的方法體是空的, 也就說明Spring框架考慮了擴展性,留了很多的口子,讓大家在框架層面繼承很多的模塊并去做自定義的實現(xiàn)
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
2.3 總結
總的來說,SpringBoot加載的Bean的時機為,點進一開始的run方法,層層遞進后,由
refreshContext(context);
進行了bean的加載,更詳細的話,那就層層遞進點進去,是在如下方法進行了bean的加載。
invokeBeanFactoryPostProcessors(beanFactory);
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
淺談java switch如果case后面沒有break,會出現(xiàn)什么情況?
這篇文章主要介紹了淺談java switch如果case后面沒有break,會出現(xiàn)什么情況?具有很好的參考價值,希望對大家有所幫助。一起跟隨想小編過來看看吧2020-09-09
Java設計模式之策略模式的使用(Strategy?Pattern)
策略模式是一種行為型設計模式,用于定義一系列算法并將每個算法封裝起來,使它們可以互相替換,從而實現(xiàn)代碼的可維護性和靈活性,策略模式包含策略接口、具體策略類和上下文類,并通過將算法的選擇與使用分離,使得算法可以獨立變化2025-03-03
詳解如何使用MyBatis實現(xiàn)數(shù)據(jù)庫的CRUD
這篇文章主要為大家詳細介紹了如何使用MyBatis實現(xiàn)數(shù)據(jù)庫的CRUD操作,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-12-12
解決JavaEE開發(fā)中字符編碼出現(xiàn)亂碼的問題
下面小編就為大家?guī)硪黄鉀QJavaEE開發(fā)中字符編碼出現(xiàn)亂碼的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07

