若是你使用過SpringBoot, 你必定會知道porfile配置所帶來的方便, 經過配置開發環境仍是生產環境, 咱們能夠十分方便的切換開發環境,部署環境,更換不一樣的數據庫。 可能爲了讓Java開發者轉向SpringBoot開發, Spring在5.x以後中止了對這個屬性的支持。因此本文也就再也不繼續描述這一屬性。node
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標籤的解析及註冊。spring
在4中標籤的解析中,對bean標籤的解析最爲複雜也最爲重要, 因此咱們從這個標籤進行深刻的分析。 不過在這以前我仍是要將以前怎麼加載這個文件的部分進行一下回憶
還記得上一部分,有一個這樣的方法:數據庫
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
若是確實對一步感興趣能夠追溯下去,這樣就能夠發現下面這段代碼:springboot
/** * 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提供的默認標籤的解析結果。ide
那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源碼深刻解析的一段:
這個部分是Spring解析配置文件的最重要的部分, 根本就是解析標籤並加載, 在使用Spring進行配置的時候不難發現, 咱們有如下幾個重要的根級標籤,bean, imort, alias, nested-beans, 下面的內容就主要介紹一下bean標籤的解析。上一個小結的結尾部分已經涉及了這個的處理,繼續上面的內容, 咱們會發現,實際上Spring首先是先經過「Bean定義解析委託」來得到了一個BeanDefinitionHolder, 在上面的分析中,咱們彷佛只注意了Element,而忘記了委託的是何時出現的,事實上這個委託是在DefaultBeanDefinitionDocumentReader在這個類中的時候就已經建立了這個委託, 而且一直經過參數的方式保存着這個委託, 知道們但願得到一個BeanDefinitionHolder的時候才真正的發揮做用,那麼這個委託具體是什麼呢?這個委託的做用是狀態的保存, 早在DefaultBeanDefinitionDocumentReader 這個類中使用的時候就經過xml解析的上下文,保存了bean標籤中的全部狀態,這些狀態包括,ui
....
等等等……
那麼BeanDefinitionHolder的做用又是什麼呢? 經過這個holder, 但是實現註冊的功能這是一個十分重要的功能,後面會具體分析這個功能。如今首先要看的是怎麼得到的這個holder呢:this
/** * Parses the supplied {@code <bean>} 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<String> 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源碼解析中的一段內容:spa
以上即是對默認標籤解析的全過程了。固然,對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是配置文件<bean>元素標籤在容器中的內部表示形式。<bean>元素標籤擁有class、scope、lazy-init等配置屬性,BeanDefinition則提供了相應的beanClass、scope、lazyInit屬性,BeanDefinition和<bean>中的屬性是一一對應的。其中RootBeanDefinition是最經常使用的實現類,它對應通常性的<bean>元素標籤,GenericBeanDefinition是自2.5版本之後新加入的bean文件配置屬性定義類,是一站式服務類。
在配置文件中能夠定義父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而沒有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition對二者共同的類信息進行抽象。
Spring經過BeanDefinition將配置文件中<bean>配置信息轉換爲容器的內部表示,並將這些BeanDefinition註冊到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRestry就像是Spring配置信息的內存數據庫,主要是以map的形式保存。後續操做直接從BeanDefinitionRegistry中讀取配置信息。
BeanDefinition 及其實現類 |
由此可知,要解析屬性首先要建立用於承載屬性的實例,也就是建立GenericBeanDefinition類型的實例。而代碼createBeanDefinition(className, parent)的做用就是實現此功能。