01.22 Spring 源碼學習(一)-容器的基礎結構

本篇筆記主要記錄了以下內容:

使用 ClassPathXmlApplicationContext,通過在 xml 註冊一個 bean,跟蹤代碼,瞭解它從配置文件的 <bean> 標籤,加載到 BeanFactory 註冊表 beanDefinitionMap 的詳細過程。/<bean>


layout: post- ClassPathXmlApplicationContext - 設置配置文件路徑 - Profile - PropertySource 接口

  • Bean 的解析和註冊具體校驗的方法獲取 bean 容器BanFactory 自定義EntityResolver配置文件加載默認標籤解析獲取 id 和 name對標籤中其它屬性的解析BeanDefinitionHolder 修飾prepareBeanFactoryinvokeBeanFactoryPostProcessorsinitMessageSourceonRefreshfinishBeanFactoryInitializationresetCommonCaches
  • 踩坑記錄Javadoc 編譯錯誤
  • 參考資料

展示的代碼摘取了一些核心方法,去掉一些默認設置和日誌輸出,還有大多數錯誤異常也去掉了,小夥伴想看詳細代碼,註釋和 demo,可以下載我上傳的筆記項目

碼雲 Gitee 地址

Github 地址

通過閱讀源碼的過程,瞭解設計者的設計思路和從中學習,對 spring 有個基礎的瞭解。

ClassPathXmlApplicationContext

ClassPathXmlApplicationContext 的繼承體系結構圖:

Spring 源碼學習(一)-容器的基礎結構

這種結構圖是通過 IDEA 編輯器的 Diagrams 功能展示的,對當前類右鍵選擇,可以看到繼承體系,繼承了哪些類和引用了哪些接口,方便我們去了解~

ClassPathXmlApplicationContext 繼承自 AbstractApplicationContext,而 AbstractRefreshableApplicationContext 是 AbstractApplicationContext 的抽象子類,使用的類註冊工廠是 DefaultListableBeanFactory,這個註冊工廠也很重要,後面會有它的介紹。

簡單來說,DefaultListableBeanFactory 是 Spring 註冊及加載 bean 的默認實現,它會將註冊的 bean放入 beanDefinitionMap 進行 key-value 形式存儲。

在圖片的右上角能看到,ResourceLoader 是它的頂層接口,表示這個類實現了資源加載功能。

構造器的代碼:

<code>public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
// 註釋 1.1 獲取資源文件
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/<code>

設置配置文件路徑

org.springframework.context.support.AbstractRefreshableConfigApplicationContext


<code>public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
// 註釋 1.2 將配置資源路徑放入 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;
}
}
/<code>

resolvePath,用途是:解析給定的路徑,用對應的佔位符(placeholder)替換佔位符

例如 new ClassPathXmlApplicationContext("classpath:config.xml");,就需要解析 classpath,變成正確路徑。

<code>protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
/<code>

我們有不同的運行環境,dev,test 或者 prod,這個時候加載的配置文件和屬性應該有所不同,這個時候就需要使用到 Environment 來進行區分。

Spring 環境和屬性是由四個部分組成:

  • Environment : 環境,由 Profile 和 PropertyResolver 組合。
  • Profile : 配置文件,可以理解為,容器裡多個配置組別的屬性和 bean,只有激活的 profile,它對應的組別屬性和 bean 才會被加載
  • PropertySource : 屬性源, 使用 CopyOnWriteArrayList 數組進行屬性對 key-value 形式存儲
  • PropertyResolver :屬性解析器,這個用途就是解析屬性

Profile

通過這個屬性,可以同時在配置文件中部署兩套配置,用來適用於生產環境和開發環境,這樣可以方便的進行切換開發、部署環境,常用來更換不同的數據庫或者配置文件。

<code>
<beans>
<property-placeholder>
/<beans>


<beans>
<property-placeholder>
/<beans>


<beans>
<property-placeholder>
/<beans>
/<code>

有兩種方式可以設置選擇使用哪套配置:

① 在 web.xml 中設置

<code><context-param>
<param-name>spring.profiles.active/<param-name>
<param-value>test/<param-value>
/<context-param>
/<code>

② 在代碼啟動時設置

<code>context.getEnvironment().setActiveProfiles("test");
/<code>

PropertySource 接口

繼承體系如圖:


Spring 源碼學習(一)-容器的基礎結構


從 PropertySource 繼承體系來看,customizePropertySources 方法的調用鏈路是從子類一直往上調用 :

AbstractEnvironment -> StandardServletEnvironment -> StandardEnvironment

最終在 StandardEnvironment 使用 CopyOnWriteArrayList 數組進行屬性存儲

<code>protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
/<code>

例如從上面可以看出,propertySourceList 將會存儲系統的參數:

Spring 源碼學習(一)-容器的基礎結構

到時這些參數就能在啟動的應用中,通過上下文 context 進行獲取

<code>((MutablePropertySources)((StandardEnvironment)context.environment).propertySources).propertySourceList
/<code>

Bean 的解析和註冊

Spring bean 的解析和註冊有一個重要的方法 refresh()

AbstractApplicationContext.refresh()


<code>public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing. (為更新準備上下文,設定一些標誌)
prepareRefresh();
// Tell the subclass to refresh the internal bean factory. (告訴子類去更新它們的 bean factory)
// 類的註冊到 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();
}
}
}
/<code>

下面會圍繞這個方法進行跟蹤和分析。


具體校驗的方法

org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties


<code>public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
/<code>

可以看到,校驗邏輯是遍歷 requiredProperties,它是一個字符 Set,默認情況下是空,表示不需要校驗任何元素,如果列表中有值,然後根據 key 獲取對應的環境變量為空,將會拋出異常,導致 Spring 容器初始化失敗。

獲取 bean 容器

在這行代碼中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

具體調用的是 :

org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory


<code>protected final void refreshBeanFactory() throws BeansException {
// 在更新時,如果發現已經存在,將會把之前的 bean 清理掉,並且關閉老 bean 容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 註釋 1.3 開始加載 (bean 註冊)
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
/<code>

這個入口方法很重要,在這一步新建了 bean 容器和解析 bean,並將 bean 註冊到容器中。


BanFactory 自定義

具體方法如下,通過這個方法,可以對工廠進行定製化設置,讓子類進行自由配置:

org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory


<code>protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
// 默認是 false,不允許覆蓋
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
// 默認是 false,不允許循環引用
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
/<code>

EntityResolver


Spring 源碼學習(一)-容器的基礎結構


接口全路徑是:org.xml.sax.EntityResolver,具體解析使用的方法是:

org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity

該方法是用於解析 schema 和 dtd,具體深究的話也很複雜,但解析 xml 不是我想了解的點,所以先跳過~


配置文件加載

入口方法:(由於有多個重名方法,所以複製路徑時,將參數的類型也拷貝了)

org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.resource>)/<org.springframework.core.io.resource>

核心方法是這兩行


<code>public int loadBeanDefinitions(String location, @Nullable Set<resource> actualResources) throws BeanDefinitionStoreException {
// 獲取資源文件(資源加載器從路徑識別資源文件)
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)
// 註釋 1.6 根據資源文件加載 bean
int count = loadBeanDefinitions(resources);
···
}
/<resource>/<code>

獲取資源文件後,開始解析資源文件(也就是一開始傳參的 config.xml),將它轉換成 Document

跟蹤代碼可以看到,進行解析的資源文件從 Resource 包裝成 EncodeResouce,為輸入流添加了字符編碼(默認為 null),體現了設計模式

  • 裝飾器模式

遍歷資源文件,進行轉換,核心方法是以下兩行:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)


<code>public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 註釋 1.7 從資源文件中獲取輸入流
InputStream inputStream = encodedResource.getResource().getInputStream();
InputSource inputSource = new InputSource(inputStream);
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
/<code>

默認標籤解析

這部分不會細說,之後再寫一篇進行補充,所以簡單的過下代碼中,是如何解析默認標籤的

  • IMPORT:導入標籤
  • ALIAS:別名標籤
  • BEAN:bean 標籤
  • NESTED_BEANS:beans 標籤(嵌套的 beans)

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement


<code>private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
/<code>

讓我們來看下如何解析 bean 標籤


獲取 id 和 name

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)


<code>public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 獲取 ID 屬性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 獲取 NAME 屬性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<string> aliases = new ArrayList<>();

if (StringUtils.hasLength(nameAttr)) {
// 名稱按照 , ; 進行分割
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
// 如果沒有指定 id,將 name 的第一個值作為 id
beanName = aliases.remove(0);
}
// 默認 null
if (containingBean == null) {
// 檢查名字是否唯一,如果 id 重複了,將拋出錯誤
// 內部 usedNames 是一個 HashSet,將會存儲加載過的 name 和 aliases
checkNameUniqueness(beanName, aliases, ele);
}
// 將公共屬性放入 AbstractBeanDefinition,具體實現在子類 GenericBeanDefinition
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
if (containingBean != null) {
// 如果 id 和 name 都是空,那個 spring 會給它生成一個默認的名稱
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}

/<string>/<code>

獲取 id 和 name 屬性的流程,按照代碼註釋一步一步往下走就清晰了

該方法主要工作流程如下:

  • 提取元素中的 id name 屬性
  • 進一步解析其它所有屬性並統一封裝到 GenericBeanDefinition 類型的實例中
  • 檢測到 bean 沒有指定 beanName 使用默認規則生成 beanName
  • 將獲取到的信息封裝到 BeanDefinitionHolder 的實例中

對標籤中其它屬性的解析

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)


<code>public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}

else {
bd.setBeanClassName(className);
}
}
return bd;
}
/<code>

初始化 BeanDefiniton 在這個方法中:(具體實現是它的子類 GenericBeanDefinition 噢~)

BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())


<code>public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
// 方法中的第三個參數是父類 bean
// 當對某個嵌套配置進行分析時,這裡需要傳遞,是為了使用父類的 scope 屬性,以備子類沒設定 scope,可以使用父類的 scope 屬性
BeanDefinitionHolder finalDefinition = definitionHolder;

// Decorate based on custom attributes first.
NamedNodeMap attributes = ele.getAttributes();
// 遍歷所有的屬性,進行屬性的修飾
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}

// Decorate based on custom nested elements.
NodeList children = ele.getChildNodes();
// 遍歷所有的子節點,修飾子元素
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;

}
/<code>

後面就是解析其它標籤的內容,之後會補坑~


BeanDefinitionHolder 修飾

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)


<code>public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
// 方法中的第三個參數是父類 bean
// 當對某個嵌套配置進行分析時,這裡需要傳遞,是為了使用父類的 scope 屬性,以備子類沒設定 scope,可以使用父類的 scope 屬性
BeanDefinitionHolder finalDefinition = definitionHolder;

// Decorate based on custom attributes first.
NamedNodeMap attributes = ele.getAttributes();
// 遍歷所有的屬性,進行屬性的修飾
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}

// Decorate based on custom nested elements.
NodeList children = ele.getChildNodes();
// 遍歷所有的子節點,修飾子元素
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
/<code>

在之前的常規屬性解析後,在這一步操作中,主要用來完成自定義標籤元素的解析,這裡繼續留個坑~


prepareBeanFactory

準備類加載器的環境,對前面獲取到的 beanFactory(ConfigurationListableBeanFactory) 進行相關的設置,包括 ClassLoader, post-processors等


invokeBeanFactoryPostProcessors

實例化並調用所有註冊的 BeanFactoryPostProcessorBean,這些是後處理器,處理類型是 BeanFactory, Spring 容器允許在實例化 bean 前,讀取 bean 信息和修改它的屬性。

相當於在實例化前,給用戶最後一次機會去修改 bean 信息。

還有一點,執行也可以有先後順序,依據這些處理器是否實現 PriorityOrdered 、Order 接口,根據 order 值進行排序。


initMessageSource

初始化此上下文的消息源


onRefresh

模板方法,可被重寫以添加特定於上下文的刷新工作。

在實例化單例之前調用特殊 bean 的初始化。(霧,不知道是啥特殊 bean ,留個坑=-=)

此實現為空。


finishBeanFactoryInitialization

完成 bean 容器的初始化,實例化所有剩餘的(非惰性初始化)單例


resetCommonCaches

真真註冊的最後一步,用來清除緩存

重置 Spring 核心中的公共內省緩存,因為我們可能再也不需要單例 bean 的元數據了


踩坑記錄

Javadoc 編譯錯誤

Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)

在編譯時,發現無法成功,提示 Javadoc 的錯誤,解決方法是在 gradle 文件中添加以下配置:

<code>tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('encoding', 'UTF-8')
}/<code>


分享到:


相關文章: