默認標籤中的自定義標籤解析
註冊解析的BeanDefinition
經過beanName註冊BeanDefinition
經過別名註冊BeanDefinition
alias標籤的解析
import標籤的解析
java
正文node
在spring源碼深度解析— IOC 之 默認標籤解析(上)中咱們已經完成了從xml配置文件到BeanDefinition的轉換,轉換後的實例是GenericBeanDefinition的實例。本文主要來看看標籤解析剩餘部分及BeanDefinition的註冊。spring
回到頂部
默認標籤中的自定義標籤解析
在上篇博文中咱們已經分析了對於默認標籤的解析,咱們繼續看戲以前的代碼,以下圖片中有一個方法:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder) 緩存
這個方法的做用是什麼呢?首先咱們看下這種場景,以下配置文件:併發
複製代碼
<bean id="demo" class="com.chenhao.spring.MyTestBean">
<property name="beanName" value="bean demo1"/>
<meta key="demo" value="demo"/>
<mybean:username="mybean"/>
</bean>
複製代碼
這個配置文件中有個自定義的標籤,decorateBeanDefinitionIfRequired方法就是用來處理這種狀況的,其中的null是用來傳遞父級BeanDefinition的,咱們進入到其方法體:app
複製代碼
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {less
BeanDefinitionHolder finalDefinition = definitionHolder;ide
// 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;
}
複製代碼
咱們看到上面的代碼有兩個遍歷操做,一個是用於對全部的屬性進行遍歷處理,另外一個是對全部的子節點進行處理,兩個遍歷操做都用到了decorateIfRequired(node, finalDefinition, containingBd);方法,咱們繼續跟蹤代碼,進入方法體:函數
複製代碼
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
// 獲取自定義標籤的命名空間
String namespaceUri = getNamespaceURI(node);
// 過濾掉默認命名標籤
if (namespaceUri != null && !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/")) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
//BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
複製代碼
首先獲取自定義標籤的命名空間,若是不是默認的命名空間則根據該命名空間獲取相應的處理器,最後調用處理器的 decorate() 進行裝飾處理。具體的裝飾過程這裏不進行講述,在後面分析自定義標籤時會作詳細說明。
回到頂部
註冊解析的BeanDefinition
對於配置文件,解析和裝飾完成以後,對於獲得的beanDefinition已經能夠知足後續的使用要求了,還剩下注冊,也就是processBeanDefinition函數中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())代碼的解析了。進入方法體:
複製代碼
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
//使用beanName作惟一標識註冊
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
//註冊全部的別名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
複製代碼
從上面的代碼咱們看到是用了beanName做爲惟一標示進行註冊的,而後註冊了全部的別名aliase。而beanDefinition最終都是註冊到BeanDefinitionRegistry中,接下來咱們具體看下注冊流程。
回到頂部
經過beanName註冊BeanDefinition
在spring中除了使用beanName做爲key將BeanDefinition放入Map中還作了其餘一些事情,咱們看下方法registerBeanDefinition代碼,BeanDefinitionRegistry是一個接口,他有三個實現類,DefaultListableBeanFactory、SimpleBeanDefinitionRegistry、GenericApplicationContext,其中SimpleBeanDefinitionRegistry很是簡單,而GenericApplicationContext最終也是使用的DefaultListableBeanFactory中的實現方法,咱們看下代碼:
複製代碼
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 校驗 beanName 與 beanDefinition
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
// 校驗 BeanDefinition
// 這是註冊前的最後一次校驗了,主要是對屬性 methodOverrides 進行校驗
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
// 從緩存中獲取指定 beanName 的 BeanDefinition
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
/**
* 若是存在
*/
if (oldBeanDefinition != null) {
// 若是存在可是不容許覆蓋,拋出異常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
//
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
// 覆蓋 beanDefinition 與 被覆蓋的 beanDefinition 不是同類
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
// 容許覆蓋,直接覆蓋原有的 BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
}
/**
* 不存在
*/
else {
// 檢測建立 Bean 階段是否已經開啓,若是開啓了則須要對 beanDefinitionMap 進行併發控制
if (hasBeanCreationStarted()) {
// beanDefinitionMap 爲全局變量,避免併發狀況
synchronized (this.beanDefinitionMap) {
//
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// 不會存在併發狀況,直接設置
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
// 從新設置 beanName 對應的緩存
resetBeanDefinition(beanName);
}
}
複製代碼
處理過程以下:
首先 BeanDefinition 進行校驗,該校驗也是註冊過程當中的最後一次校驗了,主要是對 AbstractBeanDefinition 的 methodOverrides 屬性進行校驗
根據 beanName 從緩存中獲取 BeanDefinition,若是緩存中存在,則根據 allowBeanDefinitionOverriding 標誌來判斷是否容許覆蓋,若是容許則直接覆蓋,不然拋出 BeanDefinitionStoreException 異常
若緩存中沒有指定 beanName 的 BeanDefinition,則判斷當前階段是否已經開始了 Bean 的建立階段(),若是是,則須要對 beanDefinitionMap 進行加鎖控制併發問題,不然直接設置便可。對於 hasBeanCreationStarted() 方法後續作詳細介紹,這裏不過多闡述。
若緩存中存在該 beanName 或者 單利 bean 集合中存在該 beanName,則調用 resetBeanDefinition() 重置 BeanDefinition 緩存。
其實整段代碼的核心就在於 this.beanDefinitionMap.put(beanName, beanDefinition); 。BeanDefinition 的緩存也不是神奇的東西,就是定義 一個 ConcurrentHashMap,key 爲 beanName,value 爲 BeanDefinition。
回到頂部
經過別名註冊BeanDefinition
經過別名註冊BeanDefinition最終是在SimpleBeanDefinitionRegistry中實現的,咱們看下代碼:
複製代碼
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {
if (alias.equals(name)) {
this.aliasMap.remove(alias);
}
else {
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
}
//當A->B存在時,若再次出現A->C->B時候則會拋出異常。
checkForAliasCircle(name, alias);
this.aliasMap.put(alias, name);
}
}
}
複製代碼
上述代碼的流程總結以下:
(1)alias與beanName相同狀況處理,若alias與beanName併名稱相同則不須要處理並刪除原有的alias
(2)alias覆蓋處理。若aliasName已經使用並已經指向了另外一beanName則須要用戶的設置進行處理
(3)alias循環檢查,當A->B存在時,若再次出現A->C->B時候則會拋出異常。
回到頂部
alias標籤的解析
對應bean標籤的解析是最核心的功能,對於alias、import、beans標籤的解析都是基於bean標籤解析的,接下來我就分析下alias標籤的解析。咱們回到 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,繼續看下方法體,以下圖所示:
對bean進行定義時,除了用id來 指定名稱外,爲了提供多個名稱,可使用alias標籤來指定。而全部這些名稱都指向同一個bean。在XML配置文件中,可用單獨的元素來完成bean別名的定義。咱們能夠直接使用bean標籤中的name屬性,以下:
<bean id="demo" class="com.chenhao.spring.MyTestBean" name="demo1,demo2">
<property name="beanName" value="bean demo1"/>
</bean>
在Spring還有另一種聲明別名的方式:
<bean id="myTestBean" class="com.chenhao.spring.MyTestBean"/>
<alias name="myTestBean" alias="testBean1,testBean2"/>
咱們具體看下alias標籤的解析過程,解析使用的方法processAliasRegistration(ele),方法體以下:
複製代碼
protected void processAliasRegistration(Element ele) {
//獲取beanName
String name = ele.getAttribute(NAME_ATTRIBUTE);
//獲取alias
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//註冊alias
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
複製代碼
經過代碼能夠發現解析流程與bean中的alias解析大同小異,都是講beanName與別名alias組成一對註冊到registry中。跟蹤代碼最終使用了SimpleAliasRegistry中的registerAlias(String name, String alias)方法
回到頂部
import標籤的解析
對於Spring配置文件的編寫,經歷過大型項目的人都知道,裏面有太多的配置文件了。基本採用的方式都是分模塊,分模塊的方式不少,使用import就是其中一種,例如咱們能夠構造這樣的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 id="demo" class="com.chenhao.spring.MyTestBean" name="demo1,demo2">
<property name="beanName" value="bean demo1"/>
</bean>
<import resource="lookup-method.xml"/>
<import resource="replaced-method.xml"/>
</beans>
複製代碼
applicationContext.xml文件中使用import方式導入有模塊配置文件,之後如有新模塊的加入,那就能夠簡單修改這個文件了。這樣大大簡化了配置後期維護的複雜度,並使配置模塊化,易於管理。咱們來看看Spring是如何解析import配置文件的呢。解析import標籤使用的是importBeanDefinitionResource(ele),進入方法體:
複製代碼
protected void importBeanDefinitionResource(Element ele) {
// 獲取 resource 的屬性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 爲空,直接退出
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// 解析系統屬性,格式如 :"${user.dir}"
location = 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 ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
// 絕對路徑
if (absoluteLocation) {
try {
// 直接根據地址加載相應的配置文件
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
// 相對路徑則根據相應的地址計算出絕對路徑地址
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount www.baiyiyuLet.com= getReaderContext().getReader(www.yongshiyule178.com).loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled(www.wanhongzpt.com)) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
// 解析成功後,進行監聽器激活處理
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
複製代碼
解析 import 過程較爲清晰,整個過程以下:
獲取 source 屬性的值,該值表示資源的路徑
解析路徑中的系統屬性,如」${user.dir}」
判斷資源路徑 location 是絕對路徑仍是相對路徑
若是是絕對路徑,則調遞歸調用 Bean 的解析過程,進行另外一次的解析
若是是相對路徑,則先計算出絕對路徑獲得 Resource,而後進行解析
通知監聽器,完成解析
判斷路徑
方法經過如下方法來判斷 location 是爲相對路徑仍是絕對路徑:
absoluteLocation = ResourcePatternUtils.isUrl(www.yifa5yl.com location) || ResourceUtils.toURI(location).isAbsolute();
判斷絕對路徑的規則以下:
以 classpath*: 或者 classpath: 開頭爲絕對路徑
可以經過該 location 構建出 java.net.URL爲絕對路徑
根據 location 構造 java.net.URI 判斷調用 isAbsolute(www.seocelve.com) 判斷是否爲絕對路徑
若是 location 爲絕對路徑則調用 loadBeanDefinitions(),該方法在 AbstractBeanDefinitionReader 中定義。
複製代碼
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader =www.chaoyueylgw.com getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources www.chaoyuepint.com!= null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled(www.dfyL18.com)) www.xingtuyuLeL.com{
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);
}
}
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(www.xingtuyLgw.com)) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
複製代碼
整個邏輯比較簡單,首先獲取 ResourceLoader,而後根據不一樣的 ResourceLoader 執行不一樣的邏輯,主要是可能存在多個 Resource,可是最終都會迴歸到 XmlBeanDefinitionReader.loadBeanDefinitions() ,因此這是一個遞歸的過程。
至此,import 標籤解析完畢,整個過程比較清晰明瞭:獲取 source 屬性值,獲得正確的資源路徑,而後調用 loadBeanDefinitions() 方法進行遞歸的 BeanDefinition 加載。