Spring IoC 容器初始化(2)
前情回顾
前文「 Spring IoC 容器初始化 」 以 IoC 容器中的 ClassPathXmlApplicationContext 为例进行了深入分析。
Spring 从我们的配置文件(即 application-ioc.xml)中读取到 Bean 的原始信息,将其解析为 Document 对象。由于 DOM 解析只是充当了工具(语法解析),不必舍本逐末,这里不再深入分析。
本文继续分析 Spring 如何从 Document 进行语义解析和注册 BeanDefinition。
BeanDefinition 解析和注册
前文提到,Spring 从 Document 解析和注册 BeanDefinition 是在 XmlBeanDefinitionReader#registerBeanDefinitions 方法中实现的,继续跟进这个方法:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { // ... public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 创建 BeanDefinitionDocumentReader 对象 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 将 Document 中的 Bean 信息注册到容器 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; } // ... }
该方法主要做了两件事:
-
创建 BeanDefinitionDocumentReader 对象
-
注册 Document 中的 Bean 信息
这里又有个 BeanDefinitionDocumentReader,联系前文的 BeanDefinitionReader、ResourceLoader 等,这就是面向对象编程(OOP)思想的一种典型实现,值得细品。
第二步实际调用了 DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions 方法:
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { // ... protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; // 创建解析 BeanDefinition 的代理对象 BeanDefinitionParserDelegate this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } // 解析 XML 配置文件前做的事情 preProcessXml(root); // 解析 XML 配置文件 parseBeanDefinitions(root, this.delegate); // 解析 XML 配置文件后做的事情 postProcessXml(root); this.delegate = parent; } // ... }
该方法创建了一个 BeanDefinitionParserDelegate 对象,看它的名字可知,它是用来解析 BeanDefinition 的代理对象。
在 createDelegate 方法中,还对创建的 BeanDefinitionParserDelegate 进行了初始化,主要是保存了 标签的一些默认配置,比如常见的 default-lazy-init
、 default-autowire
、 default-init-method
等。
真正对 XML 配置文件进行语义解析的是 parseBeanDefinitions 方法。而且在该方法的前后,Spring 还留出了两个方法 preProcessXml 和 postProcessXml。这两个方法都是空的,用于自定义扩展。
一个框架或软件之所以做得好,除了本身确实好用,「扩展性」也是很重要的一方面。
想起了 Tomcat 有很多参数可配置,JVM 的参数也有一大堆……
下面研究 parseBeanDefinitions 方法是如何解析 Bean 定义的。
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { // ... protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 解析 Spring 默认名称空间 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); } } // ... }
Spring 默认的名称空间是什么呢?
还记得前面的配置文件 application-ioc.xml 吗?如下:
标签中的第一行 xmlns 就是它的名称空间,也就是:http://www.springframework.org/schema/beans
这里暂且跳过 Spring 如何解析自定义标签的 parseCustomElement 方法。先来分析 Spring 如何解析默认名称空间,即 parseDefaultElement 方法:
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { // ... // 解析 Spring 默认名称空间的配置 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 解析 import 标签 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 解析 alias 标签 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // 解析 bean 标签 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // 解析 beans 标签 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } } // ... }
这里又分为四个部分,分别解析 import、alias、bean 和 beans 标签。
其实 标签是可以嵌套的,只是很少用到。若嵌套使用了 标签,会继续递归去解析。
下面以 标签为例,继续跟进:
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 通过 BeanDefinitionParserDelegate 解析出定义的 Bean 信息,并封装为 BeanDefinitionHolder 对象 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 将 BeanDefinition 注册到注册中心 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } // catch getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } } }
processBeanDefinition 方法主要做了两件事:
-
对 Document 对象中的 Bean 定义进行语义解析,并封装为 BeanDefinitionHolder。BeanDefinitionHolder 持有了 BeanDefinition,并且保存了后者的别名等信息。
-
将 BeanDefinition 注册到注册中心,这里的注册中心其实就是 IoC 容器,也就是 DefaultListableBeanFactory。
代码走到这里,还没看到 Spring 如何解析我们定义的 标签,以及 标签内部的 、 等标签……藏得够深啊!别急,马上就到了!
public class BeanDefinitionParserDelegate { @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 读取 id、name 属性 String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 读取别名 alias 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 (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); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } // 封装为持有 BeanDefinition 对象的 BeanDefinitionHolder String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; } }
parseBeanDefinitionElement 方法主要做了三件事:
-
从 Document 读取 标签中的 id 和 name 属性
-
将 Document 中的 Bean 定义转换为 BeanDefinition(实现类为 GenericBeanDefinition)
-
将 BeanDefinition 封装为 BeanDefinitionHolder 并返回
拿到 BeanDefinitionHolder 后,Spring 会将其注册到注册中心。
到这里我们还是没看到 Spring 是如何解析 标签内部的标签的,其实它们是在 parseBeanDefinitionElement 方法中实现的。
留到后面再分析吧,本文先到这里,实在是太枯燥了
小结
本文沿着上篇文章继续跟进, 好像没什么实质的东西……算了,就当 承前 启后 吧 。
欲知后事如何,且听下回分解。