是誰去讀取 BeanDefinition 的?
來源:江南一點雨
前面松哥寫文章和小夥伴們仔細捋了捋 Spring 中的 BeanDefinition 存在的幾種情況,那麼 BeanDefinition 是誰來載入呢?如果是 Java 程式碼配置,那不用說,都是註解掃描去載入 BeanDefinition 的,但是如果是 XML 或者其他格式的配置檔案,則有專門的 BeanDefinition 載入器,今天我們們就來看看這個專門的 BeanDefinition 載入器。
1. BeanDefinitionReader
這是一個介面,從名字上就能看出來專門用來讀取 BeanDefinition,介面如下:
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
@Nullable
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
這裡幾個方法我來說一下:
getRegistry:這個是獲取一個 BeanDefinition 的註冊器,就是把 BeanDefinition 載入出來之後,我們要將之註冊到容器中,所以這裡的 BeanDefinitionRegistry 說白了其實就是某一個容器。 getResourceLoader:這個是獲取配置資源的載入器,像我們平時載入 XML 配置檔案的時候,只需要寫檔名即可,系統會自動去 classpath 下查詢檔案,就依賴於這裡的 ResourceLoader。 getBeanClassLoader:這個地方就是返回類載入器。 getBeanNameGenerator:這個是獲取 beanName 的生成器,有的時候,我們在 XML 檔案中配置的時候,可能沒有給 Bean 設定 name 或者 id 屬性,那麼將會透過這個生成器去生成 beanName,這個松哥在最近錄製的 Spring 原始碼分析影片中已經詳細分析過了,這裡就不再囉嗦了。 loadBeanDefinitions:這是四個過載方法,都是根據傳入的配置檔案,去載入 BeanDefinition。雖然有四個過載方法,真正去載入 BeanDefinition 的方法其實是 loadBeanDefinitions(Resource resource)
,另外三個方法兜兜轉轉最終都會來到這個方法中。
2. AbstractBeanDefinitionReader
BeanDefinitionReader 是介面,定義了操作規範,而 AbstractBeanDefinitionReader 為這個介面提供基本的實現。AbstractBeanDefinitionReader 也還有子類,不過 AbstractBeanDefinitionReader 的子類主要是為了處理不同型別的配置檔案,如 properties 配置、XML 配置等,其他的通用操作基本上就由 AbstractBeanDefinitionReader 來完成了。
這個類的原始碼比較長,我這裡就不貼出來了。該類主要解決了 getRegistry、getResourceLoader、getBeanClassLoader 以及 getBeanNameGenerator 方法的返回值,這四個方法的返回值其實都好處理,傳入對應的值返回就行了。
loadBeanDefinitions 由於存在四個過載方法,這個方法的大致處理流程是 loadBeanDefinitions(String... locations)
呼叫 loadBeanDefinitions(String location)
,loadBeanDefinitions(String location)
呼叫 loadBeanDefinitions(Resource... resources)
,loadBeanDefinitions(Resource... resources)
則呼叫 loadBeanDefinitions(Resource resource)
,所以最後的重任都落在 loadBeanDefinitions(Resource resource)
方法上了,另外三個過載方法只是為了處理載入的配置資源而已。而真正幹活的 loadBeanDefinitions(Resource resource)
方法在 AbstractBeanDefinitionReader 類中並未重寫,這個將來要在各個具體的實現類裡邊去完成了。
2.1 PropertiesBeanDefinitionReader
這個是用來讀取 properties 配置檔案的,我們平時可能透過 XML 檔案來配置 Bean,其實 Spring 裡邊也支援使用 properties 來配置 Bean,不過 properties 配置起來比較麻煩,所以這個配置目前已經廢棄了。
2.2 XmlBeanDefinitionReader
這個就是各位小夥伴最熟悉的透過 XML 檔案來配置 Bean 了。經過前面的解析,小夥伴們已經知道了 XmlBeanDefinitionReader 是重寫了 loadBeanDefinitions(Resource resource)
方法,然後透過這個方法去載入配置檔案的。
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
可以看到,這裡將傳入的 resource 包裝為一個 EncodedResource 物件,然後呼叫另外一個過載方法,EncodedResource 物件主要是處理配置檔案的編碼問題,防止出現亂碼。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
當前類裡邊定義了一個 resourcesCurrentlyBeingLoaded 物件,這是一個 ThreadLocal,用來儲存當前正在解析的 XML 檔案,首先會嘗試將當前的要解析的 encodedResource 加入到 currentResources 集合中,如果加不進去,說明當前的 encodedResource 正在處理中,那麼就丟擲異常。
否則就去讀取 encodedResource 中的內容,並將之轉為一個 InputSource,這個 InputSource 是 XML 解析中用到的物件。然後呼叫 doLoadBeanDefinitions 方法去做進一步的解析。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
return count;
}
這個方法首先呼叫 doLoadDocument 將 XML 檔案解析為一個 Document 物件。
Document 就是 XML 解析時獲取到的檔案物件,Document 物件代表了一個 XML 檔案的模型樹,所有的其他 Node 都以一定的順序包含在 Document 物件之內,排列成一個樹狀結構,以後對 XML 檔案的所有操作都與解析器無關,直接在這個 Document 物件上進行操作即可。主流的 XML 解析方式有 SAX 解析、DOM 解析以及 Pull 解析。如果大家對於 XML 檔案解析不熟悉的話,可以自行復習,松哥這裡就不再囉嗦了。
接下來就是呼叫 registerBeanDefinitions 方法去解析這個 doc 物件,這個方法的返回值就是一共發現了多少個 BeanDefinition。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
可以看到,這裡又有了一個新的物件是 BeanDefinitionDocumentReader,這是一個介面,該介面只有一個實現類 DefaultBeanDefinitionDocumentReader,並且這個介面中也只有一個 registerBeanDefinitions 方法,這個方法就是用來解析 Document 物件並註冊成為 BeanDefinition 的。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
小夥伴們看到,從 doRegisterBeanDefinitions 方法就開始 XML 解析了,這裡首先讀取節點的 profile 屬性,由於 profile 屬性在配置的時候,可以有多個,多個的話用 ,
、;
或者空格隔開,所以這裡拿到 profile 之後,先用 MULTI_VALUE_ATTRIBUTE_DELIMITERS
去做一個字串拆分(還記得松哥之前講的 name 屬性的配置方式嗎?就是那個!),將 profile 屬性值拆分為一個陣列 specifiedProfiles,然後判斷系統當前環境是否包含 profile 中的值,如果不包含,就直接 return,後面的內容就不需要解析了。
接下來就是 XML 處理了,有預處理和後置處理兩個方法,但是這兩個目前都是空方法,沒有實現。中間的 parseBeanDefinitions 方法是 XML 解析方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element ele) {
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
這裡就是遍歷 XML 中的所有子節點,遍歷然後判斷這個子節點是預設標籤還是自定義標籤,像我們平時寫的 import
、alias
、bean
、beans
這些都算是預設標籤,而像匯入 properties 的 <context:property-placeholder location="classpath:db.properties"/>
這種標籤就屬於自定義標籤。
我們先來看預設標籤的解析:
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);
}
}
大家看到,根據不同的節點型別,呼叫不同的解析方法。如果節點是 beans,beans 中還是定義 bean 的,所以對於 beans 標籤遞迴呼叫 doRegisterBeanDefinitions 方法進行解析。
我們們看一個比較常見的 bean 標籤解析吧,其他標籤解析也都差不多:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
這個方法做了三件事:
首先呼叫 delegate.parseBeanDefinitionElement
方法去解析 Element,解析 XML 標籤中的各種屬性值,解析完成之後,將屬性值填充到 BeanDefinition 物件中並返回,這裡建立的 BeanDefinition 型別就是 GenericBeanDefinition 型別的。delegate.decorateBeanDefinitionIfRequired
方法去檢查當前 Bean 的各種屬性是否需要裝飾。什麼樣的情況需要裝飾呢?這個主要是針對一些比較特殊的屬性,例如我們之前講 Bean 的屬性注入的時候,有一種叫做 p namespace 的屬性注入,就是在標籤中新增p:username='javaboy'
來實現屬性值的注入,那麼這種屬性就需要特殊處理,這個方法就是用來解決這一類問題的。呼叫 BeanDefinitionReaderUtils.registerBeanDefinition
方法完成 BeanDefinition 的註冊,註冊到一個 Map 集合中,集合的 key 就是 beanName,集合的 value 則是 BeanDefinition 物件。
好啦,這就是 XmlBeanDefinitionReader 中的一個大致流程。
2.3 GroovyBeanDefinitionReader
這個是使用 groovy 指令碼配置 Bean,groovy 我們這裡就不展開了。
今天就主要和小夥伴們介紹了 Spring 中 XML 檔案的載入規則。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2985313/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- nodejs 讀取excel檔案,並去重NodeJSExcel
- 誰還去網咖?
- Spring原始碼解讀(1)-IOC容器BeanDefinition的載入Spring原始碼Bean
- 網易數讀:誰是中國最有錢的大學?
- 誰是老牛?誰是嫩草? WeGame與老牌網遊的故事GAM
- python是誰發明的Python
- 資料校驗---記一次讀取json配置資料,資料去重,去空JSON
- fgetcsv解決讀取結尾是“ \ r”
- Spring中眼花繚亂的BeanDefinitionSpringBean
- spring筆記-BeanDefinitionSpring筆記Bean
- BeanDefinition元資訊Bean
- synchronized到底鎖住的是誰?synchronized
- 影像的讀取,
- 他們是誰?
- 誰是中國的遊戲之都?遊戲
- 故障分析 | 是誰偷走了我的 IO
- 到底是誰在回收 JVM 的垃圾JVM
- A的女兒是B的女兒的媽媽,A是B的誰?
- 【spring原始碼系列】之【BeanDefinition】Spring原始碼Bean
- 手繪PK機繪:在閱讀/學習的應用中誰勝誰敗?
- DNS預讀取的使用DNS
- python對Excel的讀取PythonExcel
- python列表讀取的方法Python
- IT培訓機構要不要去?值不值得去?適合誰去?
- 誰是最強谷民
- 宇宙第一IDE是誰?IDE
- 誰是魚誰是餌?紅隊視角下蜜罐識別方式彙總
- 檢視oracle被鎖的表是誰鎖的Oracle
- 讀取的img的格式是uint8什麼時候轉化為float32是合理的?UI
- Chrome 正在悄悄讀取你電腦中的檔案?谷歌:這是bugChrome谷歌
- Netty原始碼學習5——服務端是如何讀取資料的Netty原始碼服務端
- Java、Python到底誰是最好的程式語言?JavaPython
- 百億美元的AI晶片市場誰是王者?AI晶片
- 查詢git某個分支是誰建立的Git
- 今年的春節檔,誰是最大贏家?
- hutool去讀excel中資料Excel
- 作為編輯,以讀者的身份去讀一本書
- spring學習:spring原始碼_BeanDefinitionSpring原始碼Bean