您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Spring源碼解密之默認標簽的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
解密
在 Spring 的 XML 配置里面有兩大類聲明,一個是默認的如 <bean id="person" class="com.battcn.bean.Person"/>
,另一類就是自定義的如<tx:annotation-driven />
,兩種標簽的解析方式差異是非常大的。parseBeanDefinitions 方法就是用來區分不同標簽所使用的解析方式。通過 node.getNamespaceURI()
方法獲取命名空間,判斷是默認命名空間還是自定義命名空間,并與 Spring 中固定的命名空間 http://www.springframework.org/schema/beans 進行比對,如果一致則采用parseDefaultElement(ele, delegate);
否則就是delegate.parseCustomElement(ele);
默認標簽的解析
parseDefaultElement 對 4 種不同的標簽 import、alias、bean、beans 做了不同的處理,其中 bean 標簽的解析最為復雜也最為重要,所以我們將從 bean 開始深入分析,如果能理解此標簽的解析過程,其他標簽的解析自然會迎刃而解。上一篇中只是簡單描述了一下,本篇我們圍繞解析模塊詳細的探討一下
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // import 標簽解析 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias 標簽解析 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean 標簽解析 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // import 標簽解析 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans標簽解析 遞歸方式 doRegisterBeanDefinitions(ele); } } }
首先我們來分析下當類中的 processBeanDefinition(ele, delegate)
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 委托BeanDefinitionDelegate類的parseBeanDefinitionElement方法進行元素解析 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 當返回的bdHolder不為空的情況下若存在默認標簽的子節點下再有自定義屬性,還需要再次對自定義標簽進行解析 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 解析完成后需要對解析后的bdHolder進行注冊,注冊操作委托給了BeanDefinitionReaderUtils的registerBeanDefinition方法 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // 最后發出響應事件,通知相關監聽器這個bean已經被加載 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
這段代碼中:
首先委托 BeanDefinitionParseDelegate 對節點做了解析,并返回了一個 BeanDefinitionHolder 的實例,在這個實例中已經包含了配置文件中配置的各種屬性了
如果在當前子節點中存在自定義屬性,則還需要對自定義標簽進行解析
解析完成后,需要對解析后的 bdHolder 進行注冊,同樣注冊操作委托給了 BeanDefinitionReaderUtils
最后發出響應事件,通知相關監聽器這個 bean 已經被加載
下面我們詳細分析下,Spring 是如何解析各個標簽和節點的
bean 標簽解析
public class BeanDefinitionParserDelegate { @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 獲取Bean標簽的ID屬性 String id = ele.getAttribute(ID_ATTRIBUTE); // 獲取Bean標簽的Name屬性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { // 將name屬性的值通過,; 進行分割 轉為字符串數字(即在配置文件中如配置多個name 在此做處理) String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; // 如果ID為空 使用配置的第一個name屬性作為ID if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { // 校驗beanName和aliases的唯一性 // 內部核心為使用usedNames集合保存所有已經使用了的beanName和alisa checkNameUniqueness(beanName, aliases, ele); } // 進一步解析其他所有屬性到GenericBeanDefinition對象中 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { // 如果bean沒有指定beanName 那么使用默認規則為此Bean生成beanName if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); // 將信息封裝到BeanDefinitionHolder對象中 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; } }
該方法主要處理了 id、name、alias 等相關屬性,生成了 beanName,并且在重載函數 parseBeanDefinitionElement(ele, beanName, containingBean)
方法中完成核心的標簽解析。
接下來重點分析parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean)
看下它是如何完成標簽解析操作的
bean 節點與屬性解析
@Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); // 獲取Bean標簽的class屬性 String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } // 獲取Bean標簽的parent屬性 String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // 創建用于承載屬性的AbstractBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 獲取bean標簽各種屬性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 解析description標簽 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解析meta標簽 parseMetaElements(ele, bd); // 解析lookup-method標簽 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析replaced-method標簽 parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析constructor-arg標簽 parseConstructorArgElements(ele, bd); // 解析property標簽 parsePropertyElements(ele, bd); // 解析qualifier標簽 parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
進一步解析其他屬性和元素(元素和屬性很多,所以這是一個龐大的工作量)并統一封裝至 GenericBeanDefinition 中, 解析完成這些屬性和元素之后,如果檢測到 bean 沒有指定的 beanName,那么便使用默認的規則為 bean 生成一個 beanName。
// BeanDefinitionParserDelegate.java protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); } public class BeanDefinitionReaderUtils { public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); // parentName可能為空 bd.setParentName(parentName); // 如果classLoader不為空 // 則使用傳入的classLoader同一虛擬機加載類對象 否則只記錄classLoader if (className != null) { if (classLoader != null) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; } }
BeanDefinition 是 <bean> 在容器中的內部表示形式,BeanDefinition 和 <bean> 是一一對應的。同時 BeanDefinition 會被注冊到 BeanDefinitionRegistry 中,BeanDefinitionRegistry 就像 Spring 配置信息的內存數據庫。
至此 createBeanDefinition(className, parent);
已經說完了,而且我們也獲得了 用于承載屬性的AbstractBeanDefinition,接下來看看 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
是如何解析 bean 中的各種標簽屬性的
public class BeanDefinitionParserDelegate { public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { // ...省略詳細代碼,該部分代碼主要就是通過 if else 判斷是否含有指定的屬性,如果有就 bd.set(attribute); return bd; } } ```
`bean` 標簽的完整解析到這就已經全部結束了,其中 `bean` 標簽下的元素解析都大同小異,有興趣的可以自己跟蹤一下源代碼看看 `qualifier、lookup-method` 等解析方式(*相對 `bean` 而言不復雜*)。自定義標簽內容較多會在下一章詳細介紹。
最后將獲取到的信息封裝到 `BeanDefinitionHolder` 實例中
``` java // BeanDefinitionParserDelegate.java @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // ... return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); }
注冊解析的 BeanDefinition
在解析完配置文件后我們已經獲取了 bean 的所有屬性,接下來就是對 bean 的注冊了
public class BeanDefinitionReaderUtils { public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 使用 beanName 做唯一標識符 String beanName = definitionHolder.getBeanName(); // 注冊bean的核心代碼 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 為bean注冊所有的別名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } } }
以上代碼主要完成兩個功能,一是使用 beanName 注冊 beanDefinition,二是完成了對別名的注冊
BeanName 注冊 BeanDefinition
public class DefaultListableBeanFactory { @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { // 注冊前的最后一次校驗,這里的校驗不同于XML文件校驗 // 主要是對于AbstractBeanDefinition屬性中的methodOverrides校驗 // 校驗methodOverrides是否與工廠方法并存或者methodOverrides對于的方法根本不存在 ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } BeanDefinition oldBeanDefinition; // 獲取緩存中的 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 + "]"); } } 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到beanDefinitionMap中 this.beanDefinitionMap.put(beanName, beanDefinition); } else { // 判斷是否已經開始創建bean if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { // 保存beanDefinition到beanDefinitionMap中 this.beanDefinitionMap.put(beanName, beanDefinition); // 更新已經注冊的beanName 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 { // 還沒開始創建bean this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); } this.frozenBeanDefinitionNames = null; } if (oldBeanDefinition != null || containsSingleton(beanName)) { // 重置beanName對應的緩存 resetBeanDefinition(beanName); } } }
對 AbstractBeanDefinition 的校驗,主要是針對 AbstractBeanDefinition 的 methodOverrides 屬性的
對 beanName 已經注冊的情況的處理,如果設置了不允許 bean 的覆蓋,則需要拋出異常,否則直接覆蓋
使用 beanName 作為 key,beanDefinition 為 Value 加入 beanDefinitionMap 存儲
如果緩存中已經存在,并且該 bean 為單例模式則清楚 beanName 對應的緩存
注冊別名
注冊好了 beanDefinition,接下來就是注冊 alias。注冊的 alias 和 beanName 的對應關系存放在了 aliasMap 中,沿著類的繼承鏈會發現 registerAlias 的方法是在 SimpleAliasRegistry 中實現的
public class SimpleAliasRegistry { /** Map from alias to canonical name */ private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16); public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); if (alias.equals(name)) { // 如果beanName與alias相同的話不記錄alias 并刪除對應的alias this.aliasMap.remove(alias); } else { String registeredName = this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { // 如果別名已經注冊過并且指向的name和當前name相同 不做任何處理 return; } // 如果alias不允許被覆蓋則拋出異常 if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } // 校驗循環指向依賴 如A->B B->C C->A則出錯 checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); } } }
通過 checkForAliasCircle()
方法來檢查 alias 循環依賴,當 A -> B 存在時,若再次出現 A -> C -> B 則會拋出異常:
protected void checkForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); } } public boolean hasAlias(String name, String alias) { for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) { String registeredName = entry.getValue(); if (registeredName.equals(name)) { String registeredAlias = entry.getKey(); return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)); } } return false; }
至此,注冊別名也完成了,主要完成了以下幾個工作
如果 beanName 與 alias 相同的話不記錄 alias 并刪除對應的 alias
如果別名已經注冊過并且指向的name和當前name相同 不做任何處理
如果別名已經注冊過并且指向的name和當前name不相同 判斷是否允許被覆蓋
校驗循環指向依賴 如A->B B->C C->A則出錯
發送通知
通知監聽器解析及注冊完成
//DefaultBeanDefinitionDocumentReader.java protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }
通過 fireComponentRegistered 方法進行通知監聽器解析及注冊完成工作,這里的實現只為擴展,當程序開發人員需要對注冊 BeanDefinition事件進行監聽時,可以通過注冊監聽器的方式并將處理邏輯寫入監聽器中,目前 Spring 中并沒有對此事件做任何處理
其中 ReaderContext 是在類 XmlBeanDefinitionReader 中調用 createReaderContext 生成的,然后調用 fireComponentRegistered()
alias 標簽解析
Spring 提供了 <alias name="person" alias="p"/>
方式來進行別名的配置,該標簽解析是在 processAliasRegistration(Element ele) 方法中完成的
public class DefaultBeanDefinitionDocumentReader { protected void processAliasRegistration(Element ele) { // 獲取 alisa 標簽 name 屬性 String name = ele.getAttribute(NAME_ATTRIBUTE); // 獲取 alisa 標簽 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 { // 進行別名注冊 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)); } } }
首先對 alias 標簽屬性進行提取校驗,校驗通過后進行別名注冊,別名注冊和 bean 標簽解析中的別名注冊一直,此處不再贅述
import 標簽解析
public class DefaultBeanDefinitionDocumentReader { protected void importBeanDefinitionResource(Element ele) { // 獲取import標簽的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); // 判斷資源是絕對路徑還是相對路徑 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 = getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources); } if (logger.isDebugEnabled()) { 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[actualResources.size()]); getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); } }
完成了對 import 標簽的處理,首先就是獲取 <import resource="beans.xml"/>
resource 屬性所表示的路徑,接著解析路徑中的屬性占位符 如 ${user.dir}
,然后判定 location 是絕對路徑還是相對路徑,如果是絕對路徑則遞歸調用 bean 的解析過程(loadBeanDefinitions(location, actualResources);)
,進行另一次解析,如果是相對路徑則計算出絕對路徑并進行解析,最后通知監聽器,解析完成
感謝各位的閱讀!關于“Spring源碼解密之默認標簽的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。