通俗理解spring原始碼(六)—— 默認標簽(import、alias、beans)的決議
上節講到了documentReader的parseDefaultElement方法,從這就開始決議各種標簽了,其中bean標簽的決議最為復雜,所以先來看看其他三個默認標簽的決議
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); } //決議beans標簽 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
1、import標簽的決議
進入importBeanDefinitionResource方法
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; } //對location中的占位符進行替換 // Resolve system properties: e.g. "${user.dir}" location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4); //判斷是絕對路徑還是相對路徑 // Discover whether the location is an absolute or relative URI 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*:" } // Absolute or relative? if (absoluteLocation) { try { //如果是絕對路徑,這里的readerContext.getReader()獲取到的就是最開始的XmlBeanDefinitionReader, //相當于會遞回呼叫loadBeanDefinitions方法,根據location加載新的組態檔 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isTraceEnabled()) { logger.trace("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 { // No URL -> considering resource location as relative to the current file. try { int importCount; //如果是相對路徑,則根據當前的Resource決議出其相對的Resource Resource relativeResource = getReaderContext().getResource().createRelative(location); //這是還是呼叫loadBeanDefinitions方法,只不過是另一個方法多載 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.isTraceEnabled()) { logger.trace("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標簽的處理,主要是對其resource屬性的處理,不管是相對路徑還是絕對路徑,最侄訓是拿到相對應的resource資源,呼叫最開頭的loadBeanDefinitions方法,只不過是呼叫的是loadBeanDefinitions不同的多載方法,核心處理是一樣的,相當于遞回呼叫,
最后發起事件的處理,以后會解釋,
2、alias標簽的決議
spring的alias標簽應該是用的比較少的,即使是在之后的注解中,也用的不多,這里還是講一下,重點是學習它的設計思路,
protected void processAliasRegistration(Element ele) { //決議name屬性 String name = ele.getAttribute(NAME_ATTRIBUTE); //決議alias屬性 String alias = ele.getAttribute(ALIAS_ATTRIBUTE); //默認校驗成功 boolean valid = true; //name非空判斷 if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } //alias空判斷 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)); } }
前面的邏輯比較簡單,重點是別名的注冊,getReaderContext().getRegistry(),就是從ReaderContext中獲取注冊中心,這里的Registry,就是最開始的DefaultListableBeanFactory,
如果是從此系列第一篇看到這里的,就應該明白ReaderContext背景關系是一直貫穿始終的,而其中又參考了XmlBeanDefinitionReader物件和Resource物件,
而DefaultListableBeanFactory不僅是一個beanDefination注冊中心,也是一個alias注冊中心,其繼承于SimpleAliasRegistry,
進入SimpleAliasRegistry.registerAlias(name, alias),
public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); //這里的map,以alias為key,name為value,保存別名與name的映射關系 synchronized (this.aliasMap) { //如果name和alias相等,就移除該別名與name的映射 if (alias.equals(name)) { this.aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); } } else { //根據alias得到已注冊的name String registeredName = this.aliasMap.get(alias); if (registeredName != null) { //如果name已經存在,就不需要注冊了 if (registeredName.equals(name)) { // An existing alias - no need to re-register return; } //判斷是否允許別名的覆寫,默認是允許的 if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } if (logger.isDebugEnabled()) { logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'"); } } //檢查回圈參考 checkForAliasCircle(name, alias); //直接往map中放 this.aliasMap.put(alias, name); if (logger.isTraceEnabled()) { logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); } } } }
別名的注冊也不是很復雜,各種檢查后,將別名與name的映射保存到map中,其中檢查別名的回圈參考要稍微復雜一點
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(); //判斷你要注冊的alias有沒有作為name被注冊過 if (registeredName.equals(name)) { //如果有,則判斷這個被注冊的name對應的alias是否和你要注冊的name相同 String registeredAlias = entry.getKey(); //如果不相同,遞回呼叫當前方法 if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) { return true; } } } return false; }
遞回呼叫hasAlias方法,判斷別名有沒有被作為name注冊過,同時已注冊過的別名是否和你要注冊的name相等,注意,這里實參name傳到了形參alias,實參alias傳到了形參name,這樣做是別有一番用意的,
看起來有點繞,舉個例子,
SimpleAliasRegistry registry = new SimpleAliasRegistry(); registry.registerAlias("a","b"); registry.registerAlias("b","a");
運行這段代碼,會拋出這個例外
Cannot register alias 'a' for name 'b': Circular reference - 'b' is a direct or indirect alias for 'a' already
像這種b作為a的別名,而a又作為b的別名的情況,就叫做別名的回圈參考,spring不允許這種情況出現,因為沒有意義,
這種情況準確來說是直接的回圈參考,所以這個報錯資訊,更明確一點的話是這樣的:
'b' is a direct alias for 'a' already
而下面這種情況
registry.registerAlias("a","b");
registry.registerAlias("b","c");
registry.registerAlias("c","a");
在運行第三段代碼時會報錯,這屬于別名的間接回圈參考,因為c雖然沒有直接注冊為a的別名,但是經過前兩段代碼,c已經間接是a的別名,
這段代碼會遞回呼叫hasAlias方法,所以報錯資訊明確一點應該是
'c' is a indirect alias for 'a' already
3、內嵌beans標簽的決議
//決議beans標簽 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse 遞回 doRegisterBeanDefinitions(ele); }
內嵌的beans標簽可能用的不多,但我們在xml組態檔中,經常會用到import標簽匯入另一個xml檔案,實際上也是匯入一個beans標簽,所以效果差不多,
類似決議import標簽,因為還需要對另一個xml做XSD校驗等操作,所以從loadBeanDefinitions開始執行,而決議內嵌beans標簽,很簡單,直接遞回呼叫最開始的doRegisterBeanDefinitions方法就可以了
這里再貼一遍之前的代碼,
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); } protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; //委托給delegate決議 this.delegate = createDelegate(getReaderContext(), root, parent); //判斷當前Beans節點是否是默認命名空間 if (this.delegate.isDefaultNamespace(root)) { //獲取beans節點的profile屬性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { //可以使用逗號或分號將當前beans標簽指定為多個profile型別 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. //判斷當前beans標簽的profile是否被激活 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("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; }
關于import、alias、beans標簽的決議就到這里了,bean標簽的決議有點復雜,而決議import、beans標簽最終都會進入bean標簽的決議,下篇會詳細介紹,
走的太遠,不要忘記為什么出發!
參考:spring原始碼深度決議
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/170361.html
標籤:Java
上一篇:【從單體架構到分布式架構】(二)請求增多,單點變集群(1):負載均衡
下一篇:Redis入門實戰(2)-安裝
