Spring原始碼系列:BeanDefinition載入(上)

glmapper發表於2018-01-31

繼上一篇BeanFactory的建立之後,其實就是BeanDefinition載入了。同樣也是在AbstractRefreshableApplicationContext類的refreshBeanFactory方法中完成:

//建立預設的DefaultListableBeanFactory工廠
DefaultListableBeanFactory beanFactory = createBeanFactory();
//設定Id
beanFactory.setSerializationId(getId());
//這個方法其實就是設定了allowBeanDefinitionOverriding和allowCircularReferences兩個屬性
customizeBeanFactory(beanFactory);

//呼叫子類的載入bean定義方法,這裡會呼叫XmlWebApplicationContext子類的複寫方法
loadBeanDefinitions(beanFactory);
複製程式碼

這裡的loadBeanDefinitions也是一個抽象方法,AbstractRefreshableApplicationContext類中並沒有給出具體的實現,二是通過子類XmlWebApplicationContext的loadBeanDefinitions完成具體實現。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 
throws BeansException, IOException
{
//建立XmlBeanDefinitionReader,並通過回撥設定到BeanFactory中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//為XmlBeanDefinitionReader配置Environment
beanDefinitionReader.setEnvironment(getEnvironment());
//為XmlBeanDefinitionReader配置ResourceLoader,
//因為DefaultResourceLoader是父類,所以this可以直接被使用
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// 允許子類提供reader的自定義初始化,然後繼續實際載入bean定義。
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
複製程式碼

initBeanDefinitionReader初始化用於載入此上下文的bean定義的bean定義讀取器;預設實現是空的。然後下面通過過載的loadBeanDefinitions來做具體的bean解析(這裡用到的是XmlBeanDefinitionReader這個解析器);使用給定的XmlBeanDefinitionReader載入bean definitions。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
//遍歷xml檔案
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
複製程式碼

此時會將我們的applicationContext.xml讀入(當然如何還有其他的spring配置檔案,同樣會一塊拿到路徑),如下圖所示:

然後繼續通過loadBeanDefinitions的過載方法繼續委託呼叫。最後交給AbstractBeanDefinitionReader的loadBeanDefinitions來完成;這個程式碼比較長,拆開一步一步來說,先看下整體的:

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取ResourceLoader資源定位器
ResourceLoader resourceLoader = getResourceLoader();
//如果定位器為null,則丟擲異常
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//是否是ResourcePatternResolver型別的定位器
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
//非ResourcePatternResolver型別的
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
複製程式碼

上面的程式碼中需要說明下為什麼要判斷當前resourceLoader是否是ResourcePatternResolver型別的,因為ResourceLoader只是提供了對classpath字首的支援。而ResourcePatternResolver提供了對classpath*字首的支援。也就是說ResourceLoader提供classpath下單資原始檔的載入,而ResourcePatternResolver提供多資原始檔的載入。
先看下假如是ResourcePatternResolver型別的(略去了部分log程式碼):

try {
//先得到我們的resources
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//解析並返回beanDefinition的數量
int loadCount = loadBeanDefinitions(resources);
//載入過程中已經被解析過的實際的Resource的填充集合
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
複製程式碼

非ResourcePatternResolver型別情況:

// Can only load single resources by absolute URL.
//只能通過絕對URL載入單個資源
Resource resource = resourceLoader.getResource(location);
//解析並返回beanDefinition的數量
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
return loadCount;
複製程式碼

然後繼續通過過載方法loadBeanDefinitions(Resource… resources)來解析(AbstractBeanDefinitionReader類中)

public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
//初始化beanDefiniton個數
int counter = 0;
//遍歷當前資源陣列
for (Resource resource : resources) {
//解析具體resource中的bean
counter += loadBeanDefinitions(resource);
}
return counter;
}
複製程式碼

然後交給子類XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)方法:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
複製程式碼

繼續通過過載方法loadBeanDefinitions(EncodedResource encodedResource)執行,這個方法我們只關注最核心的程式碼:

//獲取輸入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//資源讀取inputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//委託給doLoadBeanDefinitions來完成
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
複製程式碼

doLoadBeanDefinitions是XmlBeanDefinitionReader中的方法,來看核心程式碼:

//解析成符合w3c標準的Document
Document doc = doLoadDocument(inputSource, resource);
//繼續交給registerBeanDefinitions來處理
return registerBeanDefinitions(doc, resource);
複製程式碼

這個時候已經將loadBeanDefinitions換成registerBeanDefinitions了,也就是載入並註冊;registerBeanDefinitions同樣也是XmlBeanDefinitionReader中的方法:

public int registerBeanDefinitions(Document doc, Resource resource) throws
BeanDefinitionStoreException
{
//得到documentReader用來讀取document文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//註冊之前的bean個數
int countBefore = getRegistry().getBeanDefinitionCount();
//解析並註冊bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
複製程式碼

仍然沒有處理,繼續交給BeanDefinitionDocumentReader的registerBeanDefinitions方法來完成:

//這個實現根據“spring-beans”XSD(或DTD)解析bean定義。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
複製程式碼

還是沒處理,又細化一步,交給DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法:

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)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
複製程式碼

任何巢狀的<beans>元素都將導致此方法的遞迴。 為了正確傳播和儲存<beans> default- *屬性,請跟蹤當前(父)委託,該委託可能為null。 為了回退的目的,建立一個引用父物件的新(子)委託,然後最終重置this.delegate回到它的原始(父)引用。這個行為模仿了一堆委託,而實際上並不需要一個委託。(翻譯的有點蹩腳,大概意思就是這)

所以說DefaultBeanDefinitionDocumentReader自己也沒幹這事,又給了BeanDefinitionParserDelegate,然後就是preProcessXml()、parseBeanDefinitions()、postProcessXml()方法;其中preProcessXml()和postProcessXml()預設是空方法,自己沒有實現。具體解析在parseBeanDefinitions(root, this.delegate)中完成。

BeanDefinitionParserDelegate用於將 Document 的內容轉成 BeanDefinition例項;BeanDefinitionDocumentReader 本身不具備該功能而是交給了該類來完成。

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) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
複製程式碼

這個方法就是解析文件中根目錄下的元素:“import”,“alias”,“bean”。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析一個“import”元素,並將給定資源的bean定義載入到bean工廠中。
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//處理給定的別名元素,向登錄檔註冊別名。
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//處理給定的bean元素,解析bean定義並將其註冊到登錄檔中。
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//在給定的根<beans />元素內註冊每個bean定義。
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
複製程式碼

先來看processBeanDefinition這個方法;

BeanDefinitionHolder是一個BeanDefinition的持有者,其定義了一下變數,並對以下變數提供get和set操作。這個在後面的說道BeanDefinition體系的時候再聊。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//獲取一個BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//首先根據自定義屬性進行裝飾。
//基於自定義巢狀元素進行裝飾。
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 註冊最終裝飾的例項。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,
getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 傳送註冊事件。
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
複製程式碼

接著看registerBeanDefinition這個方法:通過給定的bean工廠註冊給定的bean definition 。

public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)

throws BeanDefinitionStoreException
{

// 在主名稱下注冊bean定義。
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// 如果有的話,註冊bean名稱的別名,
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
複製程式碼

registerBeanDefinition裡面又通過呼叫BeanDefinitionRegistry介面的實現DefaultListableBeanFactory來完成具體的註冊過程。關於DefaultListableBeanFactoryregisterBeanDefinition方法的解析邏輯將在Spring原始碼系列:BeanDefinition載入(下)中來說.


歡迎關注微信公眾號

相關文章