淺析Spring Framework框架容器啟動過程
[TOC]
構建Spring環境
採用ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
方式構建Spring容器並檢視其內部執行過程.
Spring 版本 5.1.3.RELEASE
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId >
<version>5.1.3.RELEASE</version>
</dependency>
複製程式碼
測試類
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
複製程式碼
Spring 配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="user" class="com.jimisun.learnspringboot.web.User">
<constructor-arg index="0" value="jimisun"/>
<constructor-arg index="1" value="jimisun"/>
</bean>
</beans>
複製程式碼
測試方法Main
public class Main {
public static void main(String[] args) {
// 用我們的配置檔案來啟動一個 ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
System.out.println("context 啟動成功");
User user = context.getBean(User.class);
System.out.println(user.toString());
}
}
複製程式碼
快速進入Debug檢視IOC容器構建原始碼
ApplicationContext 啟動過程中,會建立SPring Bean容器,然後初始化相關Bean,再向Bean中注入其相關依賴。
所以我們僅僅需要Debug跟蹤Main方法中ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
這一句程式碼,檢視Spring是如何建立ApplicationContext容器並將xml中的配置資訊裝配進容器的.
Spring IOC原始碼步驟分析
第一步: 檢查並設定Spring XML配置檔案
功能:設定此應用程式上下文的配置檔案位置,如果未設定;Spring可以根據需要使用預設值
setConfigLocations(configLocations);
在Main方法Debug啟動進入斷點,按F7跟進入其方法檢視,會進入ClassPathXmlApplicationContext
類的構造方法中
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
private Resource[] configResources;
// 如果已經有 ApplicationContext 並需要配置成父子關係,那麼呼叫這個構造方法
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
...
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
// 根據提供的路徑,處理成配置檔案陣列(以分號、逗號、空格、tab、換行符分割)
setConfigLocations(configLocations);
if (refresh) {
refresh(); // 核心方法 剩餘的所有步驟都在此方法中!!!
}
}
...
}
複製程式碼
首先執行"設定配置位置setConfigLocations"
的方法,解析SpringXML配置檔案
地址儲存到configLocations
屬性中。
public void setConfigLocations(@Nullable String... locations) {
//判斷配置路徑是否為null
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
//迴圈將配置檔案路徑儲存到屬性configLocations中
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
複製程式碼
第二步:執行建立Bean容器之前的準備工作
注意:除了第一步設定XML配置檔案路徑,剩餘的步驟都在該類的refresh();這個方法中執行,所以我們需要Debug跟進入這個方法
refresh();方法如下所示;因為整個SpringApplication的構建都在這個方法 裡面所以就現在這裡展現一下和大家混個臉熟.
public void refresh() throws BeansException, IllegalStateException {
 //對下面的程式碼塊新增同步鎖
synchronized (this.startupShutdownMonitor) {
 //第二步: 執行建立容器前的準備工作 :記錄下容器的啟動時間、標記“已啟動”狀態、處理配置檔案中的佔位符
prepareRefresh();
  //第三步:建立Bean容器,載入XML配置資訊 : 如果存在容器進行銷燬舊容器,建立新容器,解析XML配置檔案為一個個BeanDefinition定義註冊到新容器(BeanFactory)中,注意Bean未初始化
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//第四步: 設定 BeanFactory 的類載入器,新增幾個 BeanPostProcessor,手動註冊幾個特殊的 bean
prepareBeanFactory(beanFactory);
try {
//第五步:載入並執行後置處理器
postProcessBeanFactory(beanFactory);
//執行postProcessBeanFactory()方法
invokeBeanFactoryPostProcessors(beanFactory);
// 例項化攔截Bean建立的後置處理器beanPostProcessors
registerBeanPostProcessors(beanFactory);
//第六步: 初始化Spring容器的訊息源
initMessageSource();
//第七步:初始化Spring容器事件廣播器
initApplicationEventMulticaster();
// 空方法
onRefresh();
//第八步:註冊事件監聽器 
registerListeners();
//第九步核心方法:初始化(構造)所有在XML檔案中配置的單例非延遲載入的bean
finishBeanFactoryInitialization(beanFactory);
//第十步:清理快取,如果容器中存Bean名為lifecycleProcessor的Bean 對其進行註冊,如果不存在建立一個DefaultLifecycleProcessor進行註冊
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 摧毀已經建立的單身人士以避免懸空資源。
destroyBeans();
// 重置'有效'標誌。
cancelRefresh(ex);
// 向呼叫者傳播異常。
throw ex;
}
finally {
//重置Spring核心的工具類的快取
resetCommonCaches();
}
}
}
複製程式碼
第二步的主要工作:準備工作,記錄下容器的啟動時間、標記“已啟動”狀態、處理配置檔案中的佔位符,初始化事件屬性。
prepareRefresh();
protected void prepareRefresh() {
// 記錄啟動時間,
// 將 active 屬性設定為 true,closed 屬性設定為 false,它們都是 AtomicBoolean型別
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
//列印Logger
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// 在上下文環境中初始化任何佔位符屬性源 空方法 預設情況下不執行任何操作。
initPropertySources();
// 校驗 xml 配置檔案
getEnvironment().validateRequiredProperties();
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
複製程式碼
第三步:建立 Bean 容器,載入並註冊 Bean
主要工作:進行銷燬舊容器,建立新容器,載入BeanDefinition到BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果ApplicationContext中已經的BeanFactory屬性已經有值,銷燬此BeanFactory所有 Bean,關閉 BeanFactory,重新建立一個新的Bean容器設定給ApplicationContext的beanFactory屬性
if (hasBeanFactory()) {
//銷燬容器
destroyBeans();
//建立型別為DefaultListableBeanFactory新容器放入BeanFactory變數中
closeBeanFactory();
}
try {
//建立型別為DefaultListableBeanFactory新容器放入BeanFactory變數中
DefaultListableBeanFactory beanFactory = createBeanFactory();
//設定BeanFactory的序列化ID也就是其類名
beanFactory.setSerializationId(getId());
// 設定 BeanFactory 的兩個配置屬性:是否允許 Bean 覆蓋、是否允許迴圈引用
customizeBeanFactory(beanFactory);
//這個方法將根據配置,載入各個Bean,然後放到 BeanFactory 中 注意:這裡的載入並不是初始化這個Bean 而是以Key-value的形式儲存在beanFactory; beanName-> beanDefinition 的 map
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
複製程式碼
第四步:配置 Bean容器: prepareBeanFactory
主要工作:在Bean容器建立完畢會"手動"註冊一些特殊的 bean。官網這樣解釋: " 配置工廠的標準上下文特徵,例如上下文的ClassLoader和後處理器 "。
具體方法 : prepareBeanFactory(factory) ;
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 這裡設定為載入當前 ApplicationContext 類的類載入器
beanFactory.setBeanClassLoader(getClassLoader());
// 設定 Bean的表示式解析器
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
//預設新增一個ApplicationContextAwareProcessor的BeanPostProcessor,實現了ApplicationContextAware介面的Bean,Spring會將上下文ApplicationContext注入Bean屬性中
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 下面幾行的意思就是,如果某個 bean 依賴於以下幾個介面的實現類,在自動裝配的時候忽略它們,
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
/**
* 下面幾行就是為特殊的幾個 bean 賦值,如果有 bean 依賴了以下幾個,會注入這邊相應的值,
* 之前我們說過,"當前 ApplicationContext 持有一個 BeanFactory",這裡解釋了第一行
* ApplicationContext 還繼承了 ResourceLoader、ApplicationEventPublisher、MessageSource
* 所以對於這幾個依賴,可以賦值為 this,注意 this 是一個 ApplicationContext
* 那這裡怎麼沒看到為 MessageSource 賦值呢?那是因為 MessageSource 被註冊成為了一個普通的 bean
*/ beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
//註冊早期後處理器以檢測內部bean作為ApplicationListeners
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// 如果檢測到LoadTimeWeaver 準備編織 不是我們本章的重點無需關注
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
//預設註冊 environment systemEnvironment systemProperties的Bean 我們可以選擇覆蓋
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
```
複製程式碼
第五步: 處理自定義Bean的後置處理器
主要功能:例項化在XML配置中實現了BeanFactoryPostProcessor和BeanPostProcessors介面的Bean並執行其回撥方法.注意:此時普通的Bean仍然並沒有初始化
//例項化並呼叫XML配置中實現了BeanFactoryPostProcessors介面的的回撥
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
//例項化並呼叫XML配置中實現了BeanPostProcessors介面的的回撥
registerBeanPostProcessors(beanFactory);
複製程式碼
//註解:這裡在建立完成Bean容器後執行BeanFactoryPostProcessors介面的回撥,我們可以在Bean容器初始化完成的時候完成我們自己的業務邏輯(很少用),然後是registerBeanPostProcessors(beanFactory)方法,此方法的官方解釋是:"Register bean processors that intercept bean creation(如果存在則註冊攔截bean建立的bean後置處理器)"
複製程式碼
第六步: 初始化Spring容器的訊息源
主要功能: 初始化MessageSource。如果在此上下文中未定義,則使用parent。
// 初始化ApplicationContext的訊息源。
initMessageSource();
複製程式碼
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
//判斷beanFactory中是否有messageSource的Bean
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// 使MessageSource知道父MessageSource
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
//如果沒有父MessageSource,則此訊息源設定為父MessageSource
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// 如果沒有則建立一個預設的DelegatingMessageSource訊息源
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}
複製程式碼
第七步: 初始化Spring容器事件廣播器
PS : 在實際專案中我們很少會用到Spring的事件廣播器,因為現在都是分散式應用了局部通訊很少使用了 一篇很棒的關於Spring容器的事件講解 juejin.im/post/5a543c…
主要功能 : 註冊Spring的事件廣播器用於廣播Spring的內建事件和自定義事件
initApplicationEventMulticaster();
protected void initApplicationEventMulticaster() {
//初始化ApplicationEventMulticaster
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
//如果在上下文中沒有定義,則建立一個預設的SimpleApplicationEventMulticaster。
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
複製程式碼
第八步:註冊事件監聽器
主要功能 : 例項化實現ApplicationListener介面的bean。
// 註冊監聽器
finishBeanFactoryInitialization(beanFactory);
複製程式碼
protected void registerListeners() {
//首先註冊靜態指定的偵聽器
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
//下面是我們自定義的監聽器,Spring文件中給出的建議是 "不要在這裡初始化FactoryBeans:我們需要保留所有常規bean"
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
//使用已經註冊的事件廣播器,釋出早期的應用程式事件......
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
複製程式碼
第九步: 例項化所有的單例Bean
功能:執行到這一步,Spring.xml配置檔案中的特殊的Bean該註冊的也註冊了,該呼叫的也呼叫了,就剩下了普通的Bean了,在這一步就都例項化了.(僅僅是非延遲例項化的單例Bean),也就是說這一步就已經完成了Bean工廠(ApplicationContext)的初始化了.
// 例項化所有SPring.xml配置檔案中配置的非延遲例項化的單例Bean
finishBeanFactoryInitialization(beanFactory);
複製程式碼
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化此上下文的轉換服務
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
//如果沒有bean後處理器,則註冊預設的嵌入值解析器(例如PropertyPlaceholderConfigurer bean)之前註冊過;此時,主要用於註釋屬性值的解析度。
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 儘早初始化LoadTimeWeaverAware bean以允許儘早註冊其變換器。
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// 停止使用臨時ClassLoader進行型別匹配。
beanFactory.setTempClassLoader(null);
// 允許快取所有bean定義元資料,而不期望進一步的更改。
beanFactory.freezeConfiguration();
// 例項化所有剩餘(非延遲初始化)單例。
beanFactory.preInstantiateSingletons();
}
複製程式碼
第十步:完成ApplicationContext容器的初始化收
功能:進行相關的容器建立完成時的操作,回收相關資源
finishRefresh(); resetCommonCaches(); 複製程式碼
protected void finishRefresh() {
//清除上下文級資源快取(例如來自掃描的ASM元資料)。
clearResourceCaches();
//為此上下文初始化生命週期處理器。
initLifecycleProcessor();
// 首先將重新整理傳播到生命週期處理器。
getLifecycleProcessor().onRefresh();
// 廣播最終事件
publishEvent(new ContextRefreshedEvent(this));
// 如果處於活動狀態,請參與LiveBeansView
LiveBeansView.registerApplicationContext(this);
}
複製程式碼
//清除一些單例的工具類的快取
protected void resetCommonCaches() {
ReflectionUtils.clearCache();
AnnotationUtils.clearCache();
ResolvableType.clearCache();
CachedIntrospectionResults.clearClassLoader(getClassLoader());
}
複製程式碼
應該學習到的知識
- Spring容器中管理的Bean是Class嗎?
可以看到Bean容器中的Bean定義對映關係的Map中存放的是key(String)
->GenericBeanDefinition
的對映,那麼GenericBeanDefinition
又是什麼呢?
BeanDefinition
中儲存了我們的 Bean 資訊
,比如這個 Bean 指向的是哪個類、是否是單例的、是否懶載入、這個 Bean 依賴了哪些 Bean 等等。
- SpringBean是被ApplicationContext管理的嗎?
通過Debug
的過程中我們可以看到我們使用ClassPathXmlApplicationContext
構造的ApplicationContext
物件其實在內部維護了一個屬性名為beanFactory
,我們的SpringBean都被定義在這個屬性裡面,也就是說beanFactory
這個屬性才是容器,ApplicationContext
僅僅是做了一層包裝.那麼beanFactory
又是什麼呢?
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
}
複製程式碼
可以看到DefaultListableBeanFactory
類也是Bean容器,而且是繼承了所有其他的容器的功能,可以說是最為強大的容器;例如具有(分層,獲取多個容器,注入功能....)
本章小結
第一次參閱原始碼寫的比較慎重,其中由於身體抱恙又有所當誤,所以在釋出本章的時候也是幾天後了,總的來說本章並沒有什麼重點,僅僅是把Spring的IOC容器的啟動過程進行了標註,並未做過多底層的深度剖析,例如loadBeanDefinitions(beanFactory)Spring如何將XMl檔案的配置裝載入Bean工廠
,以及後面的每個註釋都可以新開一篇長篇大論的文章,後面儘可能的在Spring Framework深度剖析專欄中更為詳細的學習Spring整體架構原始碼
該教程所屬Java工程師之Spring Framework深度剖析專欄,本系列相關博文目錄 Java工程師之Spring Framework深度剖析專欄
本文是根據原文https://juejin.im/post/5bc5c88df265da0b001f5dee的學習筆記,將步驟更為清晰的展現