在上一篇文章,我們講了 IoC 容器初始化的準備階段,即找到 BeanDefinition 的 Resource 定位,就好比我們用水桶打水,首先要找到水源所在。找到水源之后,我們關注的就是打水的過程了,相比于之前,這個過程更加的精妙,下面我們一起來了解一下 IoC 容器初始化的第二個過程: BeanDefinition 的載入和解析
-
BeanDefinition 的載入和解析
在完成對 BeanDefinition 的 Resource 定位的分析之后,接下來我們來了解整個 BeanDefinition 信息的載入過程。對于 IoC 容器而言,這個載入相當于把定義的 BeanDefinition 在 IoC 容器中轉化成 Spring 內部表示的數據結構的過程。 IoC 容器對 Bean 的管理和依賴注入功能的實現,是通過其持有的 BeanDefinition 進行各種相關操作來完成的。這些 BeanDefinition 數據在 IoC 容器中通過一個 HashMap 來保持和維護。下面,我們從源碼出發來看一下 IoC 容器是如何對 BeanDefinition 載入的。
BeanDefinition 載入的具體交互過程如下:
BeanDefinition 載入交互過程- 在上一篇文章中我們說過,refresh() 是一個非常重要的方法,是 IoC 容器初始化的入口,那么我們找到其實現的源碼。它首先是在 FileSystemXmlApplicationContext 中調用,并在 AbstractApplicationContext 中被實現。
該方法詳細地描述了整個 ApplicationContext 的初始化過程,比如 BeanFactory 的更新等,可以看成是對 ApplicationContext 初始化的模板或執行提綱,這個執行為 Bean 的生命周期管理提供了條件。熟悉 IoC 容器使用的讀者,從這一系列調用的名字大概就能了解整個 ApplicationContext 初始化的主要內容。同時在 try-catch 之前,我們可以看到首先調用了 obtainBeanFactory 方法來獲取一個 BeanFactory,我們進去看一下發生了什么。public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { // 為防止資源占用,在異常處理中,銷毀掉前面已經生成的單例 Bean destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
- 最終到 AbstractRefreshableApplicationContext 類的 refreshBeanFactory() 方法:
在該方法中,首先判斷是否已經存在了基礎的 BeanFactory 容器,有的話就銷毀。接著調用 createBeanFactory() 方法創建了一個 DefaultListableBeanFactory。這也驗證了我們在上一文說到的,ApplicationContext 是在基礎 BeanFactory 上添加了高級容器特征的 IoC 容器,而且大多數情況下是使用 DefaultListableBeanFactory 這個具有基礎容器功能的 BeanFactory。protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
- 接著最主要的就是 loadBeanDefinitions() 方法,但是在這里這只是一個抽象方法,在上面的交互圖我們可以看到,其具體實現是在 AbstractXmlApplicationContext 中實現的。
其實到了這里,如果在上面有親自動手追蹤 BeanDefinition 的 Resource 定位的讀者,應該會對當前 AbstractXmlApplicationContext 這個類比較熟悉,因為我們上面提到的獲取 configuration 也是 在這個類中調用的。這更加可以說明 refresh() 是 IoC 容器初始化的如果,畢竟在上一個步驟中我們并沒有進入到 refresh() 這個方法里面去查看。protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 創建一個 XmlBeanDefinitionReader,并通過回調設置到 BeanFactory 中去。 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
- 接著就是 loadBeanDefinitions 調用的地方,首先得到 BeanDefinition 的 Resource 定位,其具體過程已經在上文講過,我們就不再介紹了,代碼清單如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
- 通過對以上實現原理的分析,我們可以看到,refresh() 方法啟動對 IoC 容器的初始化,具體的過程是在 XmlBeanDefinitionReader 中完成的。因為 Spring 對應不用形式的 BeanDefinition,這里使用的是 XML 方式定義,所以需要使用 XmlBeanDefinitionReader,如果使用了其他 BeanDefinition 方式,就需要使用其他中來的 BeanDefinitionReader 來完成載入工作。這里 XmlBeanDefinitionReader 的父類 AbstractBeanDefinitionReader 已經為這個載入工作做好了準備。代碼如下:
但是這里 loadBeanDefinitions 僅僅是一個接口方法,具體的實現交由各個子類去完成。下面我們進去到 XmlBeanDefinitionReader 去查看實現過程。public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
- 我們看一下源碼:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } // 這里得到XML 文件,并得到 IO 的 InputStream 準備進行讀取。 try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
- 具體的讀取過程可以在 doLoadBeanDefinitions() 方法中找到。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
- 感興趣的讀者可以到 DefaultDocumentLoader 里面看看如何得到 Document 對象,這里就不詳細分析的。我們關系的是 Spring 的 BeanDefinition 是如何按照 Spring 的 Bean 語義要求進行解析并轉化成容器內部數據結構的。這個過程是在 registerBeanDefinitions() 方法實現的,還對載入的 Bean 數量進行了統計。
可以看到,這個解析過程是在 documentReader 里面進行的,這里使用的是 DefaultBeanDefinitionDocumentReader。public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
- 我們繼續追蹤 registerBeanDefinitions() 方法,并結合最上面的交互過程,得到方法調用棧圖下圖所示:
image
我們首先進入到 DefaultBeanDefinitionDocumentReader 里面,可以看到 processBeanDefinition 方法中,調用了 BeanDefinitionParserDelegate 來最終完成這個整個解析過程,得到的結果由 BeanDefinitionHolder 來持有,源碼清單如下:
BeanDefinitionHolder 是 BeanDefinition 對象類的封裝類,封裝了 BeanDefinition、Bean 的名字和別名,用它來向 IoC 容器注冊。而具體的解析過程交由 BeanDefinitionParserDelegate 完成,感興趣的讀者可以繼續仔細最終研究。下面我們舉個例子來分析一下。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)); } }
- 我們看一下源碼:
- 我們先來看一下最常見的 Bean 元素解析:
在這里我們會看到 XML 定義文件常見到的屬性元素,如 id、name、aliase 等,把這些元素從 XML 文件轉化而來的 element 中取出來,并設置到 BeanDefinitionHolder 中去,這些屬性的解析還是比較簡單的。對于其他元素配置的解析,如各種 Bean 的屬性配置,則為一個較為復雜的過程,由 parseBeanDefinitionElement 方法完成。public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { // 這里取得 bean 元素定義里面 id、name、aliase 屬性的值。 String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); 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.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } // 這個方法引發對 bean 元素的詳細解析 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); 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); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
- 以上介紹了對 Bean 元素進行解析的過程。也就是 BeanDefinition 根據 XML 的 <bean> 定義被創建的過程。這個 BeanDefinition 可以看成 <bean> 定義的抽象。這個數據對象中封裝的數據大都是與 <bean> 定義相關的,也就是我們在定義 Bean 時看到的那些 Spring 標記,如 init-method、destroy-method 等。這個 BeanDefinition 數據類型是非常重要的,它封裝了很多基本數據,這些基本數據都是 IoC 容器需要的。 BeanDefinition 是 IoC 容器中非常核心的數據結構,而通過上述的解析,這些數據已經準備好在 IoC 容器中大顯身手了。
- 下面我們再接著跟蹤,進入 parseBeanDefinitionElement 源碼之中:
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 這里對當前的 Bean 元素進行屬性分析,并設置描述信息。 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 從名字可以看出,這里是對各種 <bean> 元素的信息進行解析的地方。 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; }
- 上面是具體生成 BeanDefinition 的地方。在這里,我們舉一個對 property 進行解析的例子,最終完成對整個 BeanDefinition 載入和解析的過程。這里是指對 Bean 元素下的 property 子元素進行解析。
public void parsePropertyElements(Element beanEle, BeanDefinition bd) { // 遍歷 Bean 元素下的定義的 property NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { // 進行詳細的解析 parsePropertyElement((Element) node, bd); } } }
public void parsePropertyElement(Element ele, BeanDefinition bd) { / 這里取得 property 的名字。 String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { error("Tag 'property' must have a 'name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); // 這里是解析 property 的過程。返回的對象對應在 Bean 中定義的 property 屬性的解析結果,這個結果會封裝到 PropertyValue 中。 try { if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); return; } Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } }
// 這里取得 peoperty 元素的值 public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { String elementName = (propertyName != null) ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"; // Should only have one child element: ref, value, list, etc. NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); } if (hasRefAttribute) { String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } else if (hasValueAttribute) { TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } else if (subElement != null) { return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. error(elementName + " must specify a ref or value", ele); return null; } }
property 子元素的解析,最終會生成對應的數據對象,比如 ManagedList、ManagedArray、ManagedSet等,這些 Managed 類是 Spring 的具體的 BeanDefinition 的數據封裝。具體的過程讀者可以去查看具體的解析過程。從一系列 parse 方法名字可以很清楚的看出是對哪種類型的解析,具體的過程我們就不再查看了。// 這里是對 property 子元素的解析過程,Array、List、Set、Map 等元素都會在這里解析 public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { if (!isDefaultNamespace(ele)) { return parseNestedCustomElement(ele, bd); } else if (nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd != null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (!StringUtils.hasLength(refName)) { // A reference to the id of another bean in the same XML file. refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); if (!StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (!StringUtils.hasLength(refName)) { error("'bean', 'local' or 'parent' is required for <ref> element", ele); return null; } } } if (!StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } else if (nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } else if (nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } else if (nodeNameEquals(ele, NULL_ELEMENT)) { // It's a distinguished null value. Let's wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { return parseArrayElement(ele, bd); } else if (nodeNameEquals(ele, LIST_ELEMENT)) { return parseListElement(ele, bd); } else if (nodeNameEquals(ele, SET_ELEMENT)) { return parseSetElement(ele, bd); } else if (nodeNameEquals(ele, MAP_ELEMENT)) { return parseMapElement(ele, bd); } else if (nodeNameEquals(ele, PROPS_ELEMENT)) { return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
- 在上一篇文章中我們說過,refresh() 是一個非常重要的方法,是 IoC 容器初始化的入口,那么我們找到其實現的源碼。它首先是在 FileSystemXmlApplicationContext 中調用,并在 AbstractApplicationContext 中被實現。
這樣逐層的解析,我們在 XML 定義的 BeanDefinition 就被整個載入到 IoC 容器中,并在容器中建立了數據映射,即在 IoC 容器創建了對應的數據結構,這些數據結構以 AbstractBeanDefinition 為入口,讓 IoC 容器進行索引、查詢和操作。但是,重要的依賴注入實際上還沒有發生,現在 IoC 容器 BeanDefinition 中存在的還只是一些靜態的配置。嚴格來說,這時候的容器還沒有完全起作用,要完全發揮容器的作用,還需要完成數據向容器的注冊。