当把文件转换为 Document 后,接下来的提取及注册bean就是我们的重头戏。当程序已经拥有 XML 文档文件的 Document 实例对象时,就会被引入 XmlBeanDefinitionReader 的这个方法。

  1. /**
  2. * 注册给定DOM文档中包含的bean定义。由loadBeanDefinitions调用。
  3. * 创建解析器类的新实例并在其上调用registerBeanDefinitions。
  4. * {@code registerBeanDefinitions} on it.
  5. * @param doc the DOM document
  6. * @param resource the resource descriptor (for context information)
  7. * @return the number of bean definitions found
  8. * @throws BeanDefinitionStoreException in case of parsing errors
  9. * @see #loadBeanDefinitions
  10. * @see #setDocumentReaderClass
  11. * @see BeanDefinitionDocumentReader#registerBeanDefinitions
  12. */
  13. public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  14. // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
  15. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  16. // 实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
  17. int countBefore = getRegistry().getBeanDefinitionCount();
  18. // 加载及注册bean
  19. documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  20. // 记录本次加载的BeanDefinition个数
  21. return getRegistry().getBeanDefinitionCount() - countBefore;
  22. }

其中的参数 doc 是通过上一节 loadDocument 加载转换出来的。在这个方法中很好地应用了面向对象单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是 BeanDefinitionDocumentReader。BeanDefinitionDocumentReader 是一个接口,而实例化的工作是在 createBeanDefinitionDocumentReader() 中完成的,而通过此方法, BeanDefinitionDocumentReader 真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入 DefaultBeanDefinitionDocumentReader 后,发现这个方法的重要目的之一就是提取 root,以便再次将 root 作为参数继续 BeanDefinition 的注册。

  1. /**
  2. * 此实现根据“Springbeans”XSD(或DTD,历史上)解析bean定义。 打开一个DOM文档;然后初始化在 级别指定的默认设置;然后解析包含的bean定义。
  3. * <p>Opens a DOM Document; then initializes the default settings
  4. * specified at the {@code <beans/>} level; then parses the contained bean definitions.
  5. */
  6. @Override
  7. public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  8. this.readerContext = readerContext;
  9. logger.debug("Loading bean definitions");
  10. Element root = doc.getDocumentElement();
  11. doRegisterBeanDefinitions(root);
  12. }

经过艰难险阻,磕磕绊绊,我们终于到了核心逻辑的底部 doRegisterBeanDefinitions(root) ,至少我们在这个方法中看到了希望。
如果说以前一直是 XML 加载解析的准备阶段,那么 doRegisterBeanDefinitions 算是真正地开始进行解析了,我们期待的核心部分真正开始了。

  1. /**
  2. * Register each bean definition within the given root {@code <beans/>} element.
  3. */
  4. @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
  5. protected void doRegisterBeanDefinitions(Element root) {
  6. // Any nested <beans> elements will cause recursion in this method. In
  7. // order to propagate and preserve <beans> default-* attributes correctly,
  8. // keep track of the current (parent) delegate, which may be null. Create
  9. // the new (child) delegate with a reference to the parent for fallback purposes,
  10. // then ultimately reset this.delegate back to its original (parent) reference.
  11. // this behavior emulates a stack of delegates without actually necessitating one.
  12. // 专门处理解析
  13. BeanDefinitionParserDelegate parent = this.delegate;
  14. this.delegate = createDelegate(getReaderContext(), root, parent);
  15. if (this.delegate.isDefaultNamespace(root)) {
  16. // 处理profile属性
  17. String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
  18. if (StringUtils.hasText(profileSpec)) {
  19. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
  20. profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  21. // We cannot use Profiles.of(...) since profile expressions are not supported
  22. // in XML config. See SPR-12458 for details.
  23. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  24. if (logger.isDebugEnabled()) {
  25. logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
  26. "] not matching: " + getReaderContext().getResource());
  27. }
  28. return;
  29. }
  30. }
  31. }
  32. /*
  33. * 这里是模板方法模式
  34. */
  35. // 解析前处理,留给子类实现
  36. preProcessXml(root);
  37. parseBeanDefinitions(root, this.delegate);
  38. // 解析后处理,留给子类实现
  39. postProcessXml(root);
  40. this.delegate = parent;
  41. }

通过上面的代码我们看到了处理流程,首先是对 profile 的处理,然后开始进行解析,可以当我们跟进preProcessXml(root)和postProcessXml(root) 发现代码是空的,既然是空的写着还有什么用呢?就像面向对象设计方法学中常说的一句话,一个类要么是面向继承设计的,要么就用 final 修饰。在DefaultBeanDefinitionDocumentReader 中并没有用 final 修饰,所以它是面向继承设计的。这两个方法正是为子类而设计的,如果读者有了解过设计模式,可以很快速地反应出这是模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader 的子类需要在 Bean 解析前后做一些处理的话,那么只需要重写这两个方法就可以了。

profile 属性的使用

我们注意到在注册bean的最开始是对 PROFILE_ATTRIBUTE 属性的解析,可能对于我们来说, profile 属性并不是很常用。让我们先了解一下这个属性。
分析 profile 前我们先了解下 profile 的用法,官方实例代码片段如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <beans profile="dev">
  7. ......
  8. </beans>
  9. <beans profile="prod">
  10. ......
  11. </beans>
  12. </beans>

集成到 Web 环境中时,在 web.xml 中加入以下代码:

  1. <context-param>
  2. <param-name>Spring.profiles.active</param-name>
  3. <param-value>dev</param-value>
  4. </context-param>

有了这个特性我们就可以在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。
了解了profile的使用再来分析代码会清晰很多,首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为 profile 是可以同时指定多个的,需要程序对其拆分,并解析每个 profile 都符合环境变量中所定义的,不定义则不会浪费性能去解析。

解析并注册 BeanDefinition

处理了 profile 后就可以进行 XML 的读取了,跟踪代码进入 parseBeanDefinitions(root, this.delegate)。

  1. /**
  2. * 解析文档中根级别的元素:“导入”、“别名”、“bean”
  3. * @param root the DOM root element of the document
  4. */
  5. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  6. // 对beans的处理
  7. if (delegate.isDefaultNamespace(root)) {
  8. NodeList nl = root.getChildNodes();
  9. for (int i = 0; i < nl.getLength(); i++) {
  10. Node node = nl.item(i);
  11. if (node instanceof Element) {
  12. Element ele = (Element) node;
  13. if (delegate.isDefaultNamespace(ele)) {
  14. // 对bean的处理
  15. parseDefaultElement(ele, delegate);
  16. }
  17. else {
  18. // 对bean的处理
  19. delegate.parseCustomElement(ele);
  20. }
  21. }
  22. }
  23. }
  24. else {
  25. delegate.parseCustomElement(root);
  26. }
  27. }

上面的代码看起来逻辑还是蛮清晰的,因为在 Spring 的 XML 配置里面有两大类Bean声明,一个是默认的

  1. <bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"/>

另一类就是自定义的,如:

  1. <tx:annotation-driven/>

而这两种的读取及解析差别是非常大的,如果采用 Spring 默认的配置,Spring 当然知道该怎么做,但是如果是自定义的,那么就需要用户实现一些接口和配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement 方法进行解析,否则使用 delegate.parseCustomElement(ele) 方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法是使用node.getNamespaceURI() 获取命名空间,并与Spring 中固定的命名空间http://www.springframework.org/schema/beans 进行比对。如果一致则认为是默认,否则就认为是自定义。而对于默认标签解析与自定义标签解析我们将会在下一篇中进行讨论。