概述BeanDefinition 的建立getValidationModeForResourceloadDocumentregisterBeanDefinitionsimport 標籤alias 標籤bean 標籤parseBeanDefinitionElementdecorateBeanDefinitionIfRequiredbeans 標籤自定義標籤參考文獻html
BeanDefinition 是一個接口,它描述了一個 Bean 實例,包括屬性值、構造方法值和繼承自它的類的更多信息。它繼承 AttributeAccessor 和 BeanMetadataElement 接口。兩個接口定義以下:java
getSource()
方法獲取。BeanDefinition 整個結構以下圖:node
BeanDefinition 在 Spring 中此接口有三種實現:RootBeanDefinition、ChildBeanDefinition 已經 GenericBeanDefinition。而這三種實現都繼承了 AbstractBeanDefinition,其中 BeanDefinition 是配置文件元素標籤在容器中的內部表示形式。元素標籤擁有 class、scope、lazy-init 等屬性,BeanDefinition 則提供了相應的 beanClass、scope、lazyInit 屬性,BeanDefinition 和中的屬性一一對應。其中 RootBeanDefinition 是最經常使用的實現類,它對應通常性的元素標籤,GenericBeanDefinition 是自2.5版本之後新加入的 bean 文件配置屬性定義類,是一站式服務的。web
在配置文件中能夠定義父和子,父用 RootBeanDefinition 表示,而子用 ChildBeanDefinition 表示,而沒有父的就使用 RootBeanDefinition 表示。AbstractBeanDefinition 對二者共同的類信息進行抽象。 spring
Spring 經過 BeanDefinition 將配置文件中的配置信息轉換爲容器的內部表示,並將這些 BeanDefinition 註冊到 BeanDefinitionRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置信息的內存數據庫,主要以 map 的形式保存,後續操做直接從 BeanDefinitionRegistry 中讀取配置信息。數據庫
以前咱們解讀了 bean 的加載過程,可是這是在 BeanDefinition 建立以後的操做,因此咱們須要知道在什麼時候何處建立的 BeanDifinition。首先看下這段代碼:app
@Test
public void MyBean(){
//解析application_context.xml文件 , 生成管理相應的Bean對象
ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
System.out.println(context.getParent());//輸出結果null
User user = (User) context.getBean("user");
System.out.println(user);
}
複製代碼
這是在講解 Spring源碼分析之ClassPathXmlApplicationContext 一文中使用的代碼,咱們從新回頭看一下當時解讀的過程,能夠發如今 obtainFreshBeanFactory()
方法中說起到 BeanDefinition 的建立,具體發生在 XmlBeanDefinitionReader 類中的 doLoadBeanDefinitions(inputSource, resource)
方法中,因此接下來咱們就接着對該方法進行詳細的解讀。dom
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 獲取 Document 實例
Document doc = this.doLoadDocument(inputSource, resource);
// 根據 Document 實例****註冊 BeanDefiniton信息
int count = this.registerBeanDefinitions(doc, resource);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
} catch (BeanDefinitionStoreException var5) {
throw var5;
} catch (SAXParseException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
} catch (SAXException var7) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
} catch (ParserConfigurationException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
} catch (IOException var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
} catch (Throwable var10) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
}
}
複製代碼
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}
複製代碼
實際上調用的是 DefaultDocumentLoader 類中的 loadDocument 方法,在此以前 getValidationModeForResource(resource)
還獲取了 xml 文件的驗證模式,因此 doLoadBeanDefinitions()
主要就是作了三件事情:ide
getValidationModeForResource(resource)
獲取 xml 文件的驗證模式;loadDocument()
根據 xml 文件獲取相應的 Document 實例;registerBeanDefinitions()
註冊 BeanDefinition 實例 該方法主要是用來檢查 xml 文件書寫語法書寫語法是否正確,要在 Spring 中使用 XSD,須要在 Spring XML 文件頭部聲明:函數
<?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">
複製代碼
關於該方法的講解,有興趣的朋友能夠查看IOC 之 獲取驗證模型
在 DocumentLoader 類中該方法定義以下:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
複製代碼
該方法接收5個參數:
getInputStream()
方法;其中參數 entityResolver 經過 getEntityResolver()
獲取,其定義以下:
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
}
}
return this.entityResolver;
}
複製代碼
若是當前 BeanDefinitionReader下的 resourceLoader 屬性不爲 null,則根據指定的 resourceLoader 建立一個 ResourceEntityResolver。若是 resourceLoader 爲null,則建立 一個 DelegatingEntityResolver,該 Resolver 委託給默認的 BeansDtdResolver 和 PluggableSchemaResolver 。
ResourceEntityResolver:繼承自 DelegatingEntityResolver,經過 ResourceLoader 來解析實體的引用。
DelegatingEntityResolver:EntityResolver 的實現,分別代理了 dtd 的 BeansDtdResolver 和 xsd 的 PluggableSchemaResolver。
BeansDtdResolver:Spring Bean DTD的 EntityResolver 實現,用於從Spring類路徑resp加載DTD、JAR文件。 從類路徑資源「 /org/springframework/beans/factory/xml/spring-beans.dtd」中獲取「 spring-beans.dtd」,不管是將其指定爲某些本地URL仍是將其指定爲「 http://www.springframework」、「org / dtd / spring-beans.dtd」。
PluggableSchemaResolver:使用一系列 Map 文件將 schema url 解析到本地 classpath 資源
關於 EntityResolver 的介紹,能夠參考SAX EntityResolver 的做用
loadDocument 方法中首先調用createDocumentBuilderFactory()
建立 DocumentBuilderFactory ,再經過該 factory 建立 DocumentBuilder,最後解析 InputSource 返回 Document 對象。核心方法爲 parse()
方法,具體實如今 DocumentBuilderImpl 類中,其定義以下:
public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(DOMMessageFormatter.formatMessage("http://www.w3.org/dom/DOMTR", "jaxp-null-input-source", (Object[])null));
} else {
if (this.fSchemaValidator != null) {
if (this.fSchemaValidationManager != null) {
this.fSchemaValidationManager.reset();
this.fUnparsedEntityHandler.reset();
}
this.resetSchemaValidator();
}
this.domParser.parse(is);
Document doc = this.domParser.getDocument();
this.domParser.dropDocumentReferences();
return doc;
}
}
複製代碼
該方法用於加載配置文件,並解析成w3c的 Document 對象,用於後續建立 BeanDefinition。其內容以下:
該方法定義以下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
複製代碼
該方法先建立一個 DefaultBeanDefinitionDocumentReader 對象,而後調用其 registerBeanDefinitions 方法,轉而進入到 doRegisterBeanDefinitions()
方法。
protected void doRegisterBeanDefinitions(Element root) {
//獲取當前類所包含的BeanDefinitionParserDelegate屬性,做爲父類生成新的組件
BeanDefinitionParserDelegate parent = this.delegate;
//若是父類爲null,組件初始化默認的lazy-init,autowire,依賴項檢查設置,init-method,destroy-method和merge設置。
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
//肯定給定節點是否指示默認名稱空間,經過比對節點包含的uri和指定的uri是否相同
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
this.preProcessXml(root);
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}
複製代碼
BeanDefinitionParserDelegate 用於解析XML bean定義,preProcessXml()
方法在當前類中屬於空方法,重點是 parseBeanDefinitions(root, this.delegate)
。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判斷根節點使用的標籤所對應的命名空間是否爲Spring提供的默認命名空間,
// 這裏根節點爲beans節點,該節點的命名空間經過其xmlns屬性進行了定義
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)) {
// 當前標籤使用的是默認的命名空間,如bean標籤,
// 則按照默認命名空間的邏輯對其進行處理
this.parseDefaultElement(ele, delegate);
} else {
// 判斷當前標籤使用的命名空間是自定義的命名空間
delegate.parseCustomElement(ele);
}
}
}
} else {
// 若是根節點使用的命名空間不是默認的命名空間,則按照自定義的命名空間進行處理
delegate.parseCustomElement(root);
}
}
複製代碼
該方法用來遍歷 root 節點下的子節點,好比說 root 節點爲節點,則遍歷它所包含的、等節點。若是根節點或者子節點採用默認命名空間的話,則調用 parseDefaultElement()
進行默認標籤解析,不然調用 delegate.parseCustomElement()
方法進行自定義解析。 接下來咱們先研究 parseDefaultElement()
方法,其定義以下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}
複製代碼
方法的功能一目瞭然,分別是對四種不一樣的標籤進行解析,分別是 import、alias、bean、beans。
對應 bean 標籤的解析是最核心的功能,對於 alias、import、beans 標籤的解析都是基於 bean 標籤解析的。
當項目工程比較大,須要維護的配置文件也會更加複雜,數量也會更多,因爲維護的人員分工不一樣,可能會產生不少個配置文件,這個時候若是想要將全部的配置文件整合到一個 XML 文件中,就須要利用 import 標籤。例如咱們能夠構造一個這樣的 application_context.xml。
<?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">
<import resource="spring-student.xml"/>
<import resource="spring-user.xml"/>
</beans>
複製代碼
application_context.xml 配置文件中使用 import 標籤的方式導入其餘模塊的配置文件,若是有配置須要修改直接修改相應配置文件便可,如有新的模塊須要引入直接增長 import 便可,這樣大大簡化了配置後期維護的複雜度,同時也易於管理。回到代碼中,Spring 利用 importBeanDefinitionResource()
方法完成對 import 標籤的解析。
protected void importBeanDefinitionResource(Element ele) {
// 獲取 resource 的屬性值
String location = ele.getAttribute("resource");
//判空處理
if (!StringUtils.hasText(location)) {
this.getReaderContext().error("Resource location must not be empty", ele);
} else {
// 解析系統屬性,格式如 :"${user.dir}",也就是佔位符替換
location = this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet(4);
// 判斷 location 是相對路徑仍是絕對路徑
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException var11) {
}
int importCount;
// 絕對路徑
if (absoluteLocation) {
try {
// 直接根據地質加載相應的配置文件
importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException var10) {
this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, var10);
}
} else {
try {
// 相對路徑則根據相應的地質計算出絕對路徑地址
Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
String baseLocation = this.getReaderContext().getResource().getURL().toString();
importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException var8) {
this.getReaderContext().error("Failed to resolve current resource location", ele, var8);
} catch (BeanDefinitionStoreException var9) {
this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, var9);
}
}
// 解析成功後,進行監聽器激活處理
Resource[] actResArray = (Resource[])actualResources.toArray(new Resource[0]);
this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));
}
}
複製代碼
解析 import 過程較爲清晰,整個過程以下:
判斷路徑 方法經過如下方法來判斷 location 是爲相對路徑仍是絕對路徑:
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
複製代碼
判斷絕對路徑的規則以下:
java.net.URL
爲絕對路徑java.net.URI
判斷調用 isAbsolute()
判斷是否爲絕對路徑絕對路徑 若是 location 爲絕對路徑則調用 loadBeanDefinitions()
,該方法在 AbstractBeanDefinitionReader 中定義。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int count;
if (resourceLoader instanceof ResourcePatternResolver) {
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
count = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
} catch (IOException var6) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
}
} else {
Resource resource = resourceLoader.getResource(location);
count = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
}
複製代碼
整個邏輯比較簡單,首先獲取 ResourceLoader,而後根據不一樣的 ResourceLoader 執行不一樣的邏輯,主要是可能存在多個 Resource,可是最終都會迴歸到 XmlBeanDefinitionReader.loadBeanDefinitions()
,因此這是一個遞歸的過程。
相對路徑 若是是相對路徑則會根據相應的 Resource 計算出相應的絕對路徑,而後根據該路徑構造一個 Resource,若該 Resource 存在,則調用 XmlBeanDefinitionReader.loadBeanDefinitions()
進行 BeanDefinition 加載,不然構造一個絕對 location ,調用 AbstractBeanDefinitionReader.loadBeanDefinitions()
方法,與絕對路徑過程同樣。
至此,import 標籤解析完畢,整個過程比較清晰明瞭:獲取 source 屬性值,獲得正確的資源路徑,而後調用 loadBeanDefinitions()
方法進行遞歸的 BeanDefinition 加載。
對 bean 進行定義時,除了用id來 指定名稱外,爲了提供多個名稱,可使用 name 屬性來指定。而全部這些名稱都指向同一個 bean。在 XML 配置文件中,可用單獨的元素來完成 bean 別名的定義。咱們能夠直接使用 bean 標籤中的 name 屬性,以下:
<bean id="user" class="com.msdn.bean.User" name="user2,user3">
<constructor-arg name="name" value="hresh" />
</bean>
複製代碼
在 Spring 還可使用 alias 來聲明別名:
<bean id="user" class="com.msdn.bean.User" />
<alias name="user" alias="user2,user3"/>
複製代碼
咱們具體看下alias標籤的解析過程,解析使用的方法 processAliasRegistration(ele)
,方法體以下:
protected void processAliasRegistration(Element ele) {
//獲取beanName
String name = ele.getAttribute("name");
//獲取alias
String alias = ele.getAttribute("alias");
boolean valid = true;
if (!StringUtils.hasText(name)) {
this.getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
this.getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
this.getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception var6) {
this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, var6);
}
this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele));
}
}
複製代碼
經過代碼能夠發現主要是將 beanName 與別名 alias 組成一對註冊到 registry 中。跟蹤代碼最終使用了 SimpleAliasRegistry 中的 registerAlias(String name, String alias)
方法 。
public void resolveAliases(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
synchronized(this.aliasMap) {
Map<String, String> aliasCopy = new HashMap(this.aliasMap);
aliasCopy.forEach((alias, registeredName) -> {
String resolvedAlias = valueResolver.resolveStringValue(alias);
String resolvedName = valueResolver.resolveStringValue(registeredName);
if (resolvedAlias != null && resolvedName != null && !resolvedAlias.equals(resolvedName)) {
if (!resolvedAlias.equals(alias)) {
String existingName = (String)this.aliasMap.get(resolvedAlias);
if (existingName != null) {
if (existingName.equals(resolvedName)) {
this.aliasMap.remove(alias);
return;
}
throw new IllegalStateException("Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName + "': It is already registered for name '" + registeredName + "'.");
}
//當A->B存在時,若再次出現A->C->B時候則會拋出異常。
this.checkForAliasCircle(resolvedName, resolvedAlias);
this.aliasMap.remove(alias);
this.aliasMap.put(resolvedAlias, resolvedName);
} else if (!registeredName.equals(resolvedName)) {
this.aliasMap.put(alias, resolvedName);
}
} else {
this.aliasMap.remove(alias);
}
});
}
}
複製代碼
上述代碼的流程總結以下:
Spring 中最複雜也是最重要的是 bean 標籤的解析過程。 在方法 parseDefaultElement()
中,若是遇到標籤 爲 bean 則調用 processBeanDefinition()
方法進行 bean 標籤解析,以下:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
複製代碼
整個過程分爲四步:
接下來咱們就針對具體的方法進行分析,首先咱們從元素解析及信息提取開始,首先進入 BeanDefinitionParserDelegate 類的 parseBeanDefinitionElement 方法。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute("id");
String nameAttr = ele.getAttribute("name");
List<String> aliases = new ArrayList();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
beanName = (String)aliases.remove(0);
if (this.logger.isTraceEnabled()) {
this.logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
}
}
// 檢查 name 的惟一性
if (containingBean == null) {
this.checkNameUniqueness(beanName, aliases, ele);
}
// 解析屬性,構造 AbstractBeanDefinition
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 若是 beanName 不存在,則根據條件構造一個 beanName
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
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);
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
}
} catch (Exception var9) {
this.error(var9.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 封裝 BeanDefinitionHolder
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} else {
return null;
}
}
複製代碼
上述方法就是對默認標籤解析的全過程了,咱們分析下當前層完成的工做:
parseBeanDefinitionElement(ele, beanName, containingBean)
方法用來解析標籤中的屬性,源碼以下:
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute("class")) {
className = ele.getAttribute("class").trim();
}
String parent = null;
if (ele.hasAttribute("parent")) {
parent = ele.getAttribute("parent");
}
try {
//建立用於承載屬性的AbstractBeanDefinition類型的GenericBeanDefinition實例
AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);
//硬編碼解析bean的各類屬性
this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//設置description屬性
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));
//解析元素
this.parseMetaElements(ele, bd);
//解析lookup-method屬性
this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replace-method屬性
this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析構造函數的參數
this.parseConstructorArgElements(ele, bd);
//解析properties子元素
this.parsePropertyElements(ele, bd);
//解析qualifier子元素
this.parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(this.extractSource(ele));
AbstractBeanDefinition var7 = bd;
return var7;
} catch (ClassNotFoundException var13) {
this.error("Bean class [" + className + "] not found", ele, var13);
} catch (NoClassDefFoundError var14) {
this.error("Class that bean class [" + className + "] depends on not found", ele, var14);
} catch (Throwable var15) {
this.error("Unexpected failure during bean definition parsing", ele, var15);
} finally {
this.parseState.pop();
}
return null;
}
複製代碼
在 BeanDefinitionParserDelegate.parseBeanDefinitionElement()
中完成 Bean 的解析,返回的是一個已經完成對標籤解析的 BeanDefinition 實例。在該方法內部,首先調用 createBeanDefinition()
方法建立一個用於承載屬性的 GenericBeanDefinition 實例,以下:
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader());
}
複製代碼
實現細節在 BeanDefinitionReaderUtils 類中,以下:
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;
}
複製代碼
該方法主要是設置 parentName、className、classLoader。 建立完 GenericBeanDefinition 實例後,再調用 parseBeanDefinitionAttributes()
,該方法將建立好的 GenericBeanDefinition 實例當作參數,對 Bean 標籤的全部屬性進行解析,以下:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
if (ele.hasAttribute("singleton")) {
this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
} else if (ele.hasAttribute("scope")) {
bd.setScope(ele.getAttribute("scope"));
} else if (containingBean != null) {
bd.setScope(containingBean.getScope());
}
if (ele.hasAttribute("abstract")) {
bd.setAbstract("true".equals(ele.getAttribute("abstract")));
}
String lazyInit = ele.getAttribute("lazy-init");
if (this.isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit("true".equals(lazyInit));
String autowire = ele.getAttribute("autowire");
bd.setAutowireMode(this.getAutowireMode(autowire));
String autowireCandidate;
if (ele.hasAttribute("depends-on")) {
autowireCandidate = ele.getAttribute("depends-on");
bd.setDependsOn(StringUtils.tokenizeToStringArray(autowireCandidate, ",; "));
}
autowireCandidate = ele.getAttribute("autowire-candidate");
String destroyMethodName;
if (this.isDefaultValue(autowireCandidate)) {
destroyMethodName = this.defaults.getAutowireCandidates();
if (destroyMethodName != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(destroyMethodName);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
} else {
bd.setAutowireCandidate("true".equals(autowireCandidate));
}
if (ele.hasAttribute("primary")) {
bd.setPrimary("true".equals(ele.getAttribute("primary")));
}
if (ele.hasAttribute("init-method")) {
destroyMethodName = ele.getAttribute("init-method");
bd.setInitMethodName(destroyMethodName);
} else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
if (ele.hasAttribute("destroy-method")) {
destroyMethodName = ele.getAttribute("destroy-method");
bd.setDestroyMethodName(destroyMethodName);
} else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
if (ele.hasAttribute("factory-method")) {
bd.setFactoryMethodName(ele.getAttribute("factory-method"));
}
if (ele.hasAttribute("factory-bean")) {
bd.setFactoryBeanName(ele.getAttribute("factory-bean"));
}
return bd;
}
複製代碼
從上面代碼咱們能夠清晰地看到對 Bean 標籤屬性的解析,這些屬性咱們或多或少用到過。 完成 Bean 標籤基本屬性解析後,會依次調用 parseMetaElements()
、parseLookupOverrideSubElements()
、parseReplacedMethodSubElements()
對子元素 meta、lookup-method、replace-method 完成解析。關於這三個屬性的解析,能夠參考IOC 之解析 bean 標籤:meta、lookup-method、replace-method。以後還有對 constructor-arg 、property、qualifier 三個子元素的解析,一樣能夠參考IOC 之解析 bean 標籤:constructor-arg、property 子元素。
當 parseBeanDefinitionElement()
方法完成默認標籤的解析以後,若是解析成功,則首先調用 BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired()
完成自定義標籤元素解析。關於自定義標籤的解析分爲兩種: Spring中默認標籤下的自定義標籤和自定義標籤中不一樣標籤的處理器,這些都是以標籤所提供的命名空間爲基礎的,相關實現方式會在以後的文章作介紹。接下來咱們所介紹的是標籤下的自定義標籤。
具體實現是在 BeanDefinitionParserDelegate 類中,
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = originalDef;
NamedNodeMap attributes = ele.getAttributes();
// 遍歷節點,查看是否有適用於裝飾的屬性
for(int i = 0; i < attributes.getLength(); ++i) {
Node node = attributes.item(i);
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}
NodeList children = ele.getChildNodes();
// 遍歷子節點,查看是否有適用於修飾的子元素
for(int i = 0; i < children.getLength(); ++i) {
Node node = children.item(i);
if (node.getNodeType() == 1) {
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
複製代碼
遍歷節點(子節點),調用 decorateIfRequired()
裝飾節點(子節點)。
public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
// 獲取自定義標籤的命名空間
String namespaceUri = this.getNamespaceURI(node);
// 過濾掉默認命名標籤
if (namespaceUri != null && !this.isDefaultNamespace(namespaceUri)) {
// 獲取相應的處理器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
// 進行裝飾處理
BeanDefinitionHolder decorated = handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {
return decorated;
}
} else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
return originalDef;
}
複製代碼
首先獲取自定義標籤的命名空間,若是不是默認的命名空間則根據該命名空間獲取相應的處理器,最後調用處理器的 decorate()
方法進行裝飾處理,以下是該方法的實現:
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
// 獲取當前自定義屬性或子標籤註冊的BeanDefinitionDecorator對象
BeanDefinitionDecorator decorator = this.findDecoratorForNode(node, parserContext);
// 調用自定義的BeanDefinitionDecorator.decorate()方法進行裝飾,
// 這裏就是咱們實現的UserDefinitionDecorator類,該案例在下一篇文章中
return decorator != null ? decorator.decorate(node, definition, parserContext) : null;
}
複製代碼
自定義屬性或自定義子標籤查找當前 Decorator 的方法是須要對屬性或子標籤進行分別判斷的,以下是 findDecoratorForNode()
的實現:
private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
BeanDefinitionDecorator decorator = null;
// 獲取當前標籤或屬性的局部鍵名
String localName = parserContext.getDelegate().getLocalName(node);
// 判斷當前節點是屬性仍是子標籤,根據狀況不一樣獲取不一樣的Decorator處理邏輯
if (node instanceof Element) {
decorator = (BeanDefinitionDecorator)this.decorators.get(localName);
} else if (node instanceof Attr) {
decorator = (BeanDefinitionDecorator)this.attributeDecorators.get(localName);
} else {
parserContext.getReaderContext().fatal("Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node);
}
if (decorator == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " + (node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node);
}
return decorator;
}
複製代碼
對於 BeanDefinitionDecorator 處理邏輯的查找,能夠看到,其會根據節點的類型進行判斷,根據不一樣的狀況獲取不一樣的 BeanDefinitionDecorator 處理對象。
上述代碼適用於默認標籤下的自定義子標籤或自定義屬性,關於這二者的使用方法,我會在下一篇文章進行代碼展現。
BeanDefinitionReaderUtils.registerBeanDefinition()
對 bdHolder 進行註冊,咱們看一下其具體實現。
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
//獲取bean定義的名稱
String beanName = definitionHolder.getBeanName();
//將 beanName與實例化的BeanDefinition綁定在一塊兒
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//若是有別名,則將beanName與別名相關聯
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
複製代碼
該標籤就是再次調用 doRegisterBeanDefinitions()
方法,查找默認標籤或自定義標籤。
關於自定義標籤的解讀在下一章節學習,有興趣的朋友能夠閱讀一下:Spring IoC自定義標籤解析
http://cmsblogs.com/?p=2724
https://www.cnblogs.com/java-chen-hao/p/11115300.html#_label2_1
https://my.oschina.net/zhangxufeng/blog/1815705