IoC 容器的初始化之 BeanDefinition 的载入和
2017-11-27 本文已影响0人
偷星辰夜
在上一篇文章,我们讲了 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 中存在的还只是一些静态的配置。严格来说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需要完成数据向容器的注册。