摘要:進一步解析其他所有屬性并統一封裝至類型的實例中。是一個接口,在中存在三種實現以及。通過將配置文件中配置信息轉換為容器的內部表示,并將這些注冊到中。容器的就像是配置信息的內存數據庫,主要是以的形式保存。而代碼的作用就是實現此功能。
前言:繼續前一章。 一、porfile 屬性的使用
如果你使用過SpringBoot, 你一定會知道porfile配置所帶來的方便, 通過配置開發環境還是生產環境, 我們可以十分方便的切換開發環境,部署環境,更換不同的數據庫。 可能為了讓Java開發者轉向SpringBoot開發, Spring在5.x之后停止了對這個屬性的支持。所以本文也就不再繼續描述這一屬性。
二、bean標簽的解析及注冊Spring中的標簽分為默認標簽和自定義標簽兩種,而這兩種標簽的用法及解析方式存在著很大的不同,默認標簽是在parseDefaultElement中進行的,函數中的功能一目了然,分別對4種標簽(import, alias、bean、beans)做了不同的處理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
我們不得不承認,Spring5.x提倡我們更少的使用xml文件,而是更多的使用注解進行配置,而且如果你經常使用Springboot的話,那么你肯定知道習慣優于約定,并且springboot中只需要一個配置文件,雖然這有時根本無法滿足需求,這里不做關于springboot的更多的說明。不過這并不影響Spring內部的實現,現在主要還是從xml文件分析一下bean標簽的解析及注冊。
在4中標簽的解析中,對bean標簽的解析最為復雜也最為重要, 所以我們從這個標簽進行深入的分析。不過在這之前我還是要將之前怎么加載這個文件的部分進行一下回憶
還記得上一部分,有一個這樣的方法:
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). *Opens a DOM Document; then initializes the default settings * specified at the {@code
} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
如果確實對一步感興趣可以追溯下去,這樣就可以發現下面這段代碼:
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ 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); } }
這段代碼可能有點難以理解,不過當知道了if(delegate.isDefaultNamespace(ele)) 這個方法的作用就知道了,這其實就是在對標簽進行一次處理而已, 是默認標簽的就交給默認的處理方式,是自定義標簽的話就換另一種處理方式。這就是這個方法中做的事了。
public boolean isDefaultNamespace(Node node) { return isDefaultNamespace(getNamespaceURI(node)); }
這里的Node節點定義了所有的Spring提供的默認標簽的解析結果。
那parseDefaultElement(ele, delegate)這個方法又在做些什么呢?其實不過是對根級節點的標簽進行解析分類而已,現在我們先分析一下bean標簽, 所以現在只看針對于標簽做了些什么。
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } 進入這個方法 /** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name "" + bdHolder.getBeanName() + """, ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
這里使用Spring源碼深入解析的一段:三、解析BeanDefiniton
這個部分是Spring解析配置文件的最重要的部分, 根本就是解析標簽并加載, 在使用Spring進行配置的時候不難發現, 我們有以下幾個重要的根級標簽,bean, imort, alias, nested-beans, 下面的內容就主要介紹一下bean標簽的解析。上一個小結的結尾部分已經涉及了這個的處理,繼續上面的內容, 我們會發現,實際上Spring首先是先通過“Bean定義解析委托”來獲得了一個BeanDefinitionHolder, 在上面的分析中,我們似乎只注意了Element,而忘記了委托的是什么時候出現的,事實上這個委托是在DefaultBeanDefinitionDocumentReader在這個類中的時候就已經創建了這個委托, 并且一直通過參數的方式保存著這個委托, 知道們希望獲得一個BeanDefinitionHolder的時候才真正的發揮作用,那么這個委托具體是什么呢?這個委托的作用是狀態的保存, 早在DefaultBeanDefinitionDocumentReader 這個類中使用的時候就通過xml解析的上下文,保存了bean標簽中的所有狀態,這些狀態包括,
....
等等等……
那么BeanDefinitionHolder的作用又是什么呢? 通過這個holder, 可是實現注冊的功能這是一個十分重要的功能,后面會具體分析這個功能。現在首先要看的是怎么獲得的這個holder呢:
/** * Parses the supplied {@code} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML "id" specified - using "" + beanName + "" as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { 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.isTraceEnabled()) { logger.trace("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); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
此處引用Spring源碼解析中的一段內容:
以上便是對默認標簽解析的全過程了。當然,對Spring的解析猶如洋蔥剝皮一樣,一層一層的進行,盡管現在只能看到對屬性id以及name的解析,但是很慶幸,思路我們已經了解了。在開始對屬性進行全面分析之前, Spring在最外層做了一個當前成的功能架構, 在當前成完成的主要工作包括以下的內容。
(1)提取元素中的id和name屬性。
(2)進一步解析其他所有屬性并統一封裝至GenericBeanDefinition類型的實例中。
(3)如果檢測到bean沒有指定beanName,那么使用默認規則為此Bean生成beanName。
(4)將檢測到的信息封裝到BeanDefintionHolder的實例中。
繼續跟進代碼:
/** * Parse the bean definition itself, without regard to name or aliases. May return * {@code null} if problems occurred during the parsing of the bean definition. */ @Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { AbstractBeanDefinition bd = createBeanDefinition(className, parent); parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); parseConstructorArgElements(ele, bd); parsePropertyElements(ele, bd); 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; }
通過對代碼的跟蹤,事實上,我們很容易發現,這里的className就是從上一個方法中的通過解析別名得到beanName,在這里通過beanName又從已存的元素中獲取得到的。同樣的這個標簽的父級元素parent也是這樣獲取得到。而接下來的操作也就是對各種屬性的具體的解析操作了,諸如:me他, lookup-method, replace-method, property, qualifier子元素等。
BeanDefinition是一個接口,在Spring中存在三種實現:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三種實現均繼承了AbstractBeanDefinition,其中BeanDefinition是配置文件元素標簽在容器中的內部表示形式。 元素標簽擁有class、scope、lazy-init等配置屬性,BeanDefinition則提供了相應的beanClass、scope、lazyInit屬性,BeanDefinition和 中的屬性是一一對應的。其中RootBeanDefinition是最常用的實現類,它對應一般性的 元素標簽,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置屬性定義類,是一站式服務類。
在配置文件中可以定義父和子 ,父 用RootBeanDefinition表示,而子 用ChildBeanDefinition表示,而沒有父 的 就使用RootBeanDefinition表示。AbstractBeanDefinition對兩者共同的類信息進行抽象。
Spring通過BeanDefinition將配置文件中配置信息轉換為容器的內部表示,并將這些BeanDefinition注冊到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRestry就像是Spring配置信息的內存數據庫,主要是以map的形式保存。后續操作直接從BeanDefinitionRegistry中讀取配置信息。
BeanDefinition 及其實現類 |
由此可知,要解析屬性首先要創建用于承載屬性的實例,也就是創建GenericBeanDefinition類型的實例。而代碼createBeanDefinition(className, parent)的作用就是實現此功能。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72751.html
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
摘要:對于開發者來說,無疑是最常用也是最基礎的框架之一。概念上的東西還是要提一嘴的用容器來管理。和是容器的兩種表現形式。定義了簡單容器的基本功能。抽象出一個資源類來表示資源調用了忽略指定接口的自動裝配功能委托解析資源。 對于Java開發者來說,Spring無疑是最常用也是最基礎的框架之一。(此處省略1w字吹Spring)。相信很多同行跟我一樣,只是停留在會用的階段,比如用@Component...
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現在軟件開發中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:下面跟蹤代碼到這個實現中看看是怎么做的在實例化的過程中,在構造函數中調用了其超類的構造函數,而在超類中對其所處換環境進行的判斷,所謂的環境呢,事實上指得就是是通過,還是通過加載的上下文,這也就意味著不同方式加載可能存在某些不同。 前言 本文基于《Spring源碼深度解析》學習, 《Spring源碼深度解析》講解的Spring版本低于Spring3.1,當前閱讀的版本為Spring5.x...
閱讀 1266·2021-11-24 09:39
閱讀 1517·2021-09-07 09:59
閱讀 3479·2019-08-30 15:54
閱讀 2474·2019-08-30 11:00
閱讀 2669·2019-08-29 15:06
閱讀 2160·2019-08-26 13:52
閱讀 427·2019-08-26 13:24
閱讀 2489·2019-08-26 12:20