共计 7342 个字符,预计需要花费 19 分钟才能阅读完成。
本文首发于微信公众号【WriteOnRead】,欢送关注。
前情回顾
前文「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 进行了初始化,次要是保留了 <beans> 标签的一些默认配置,比方常见的 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 吗?如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 测试循环依赖 -->
<bean id="person" class="com.jaxer.doc.ioc.Person">
<property name="pet" ref="dog"/>
</bean>
<bean id="dog" class="com.jaxer.doc.ioc.Dog">
<property name="owner" ref="person"/>
<property name="age" value="1"/>
</bean>
</beans>
<beans> 标签中的第一行 xmlns 就是它的名称空间,也就是:http://www.springframework.or…
这里暂且跳过 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 标签。
其实 <beans> 标签是能够嵌套的,只是很少用到。若嵌套应用了 <beans> 标签,会持续递归去解析。
上面以 <bean> 标签为例,持续跟进:
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 如何解析咱们定义的 <bean> 标签,以及 <bean> 标签外部的 <property>、<constructor-arg> 等标签……藏得够深啊!别急,马上就到了!
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<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 (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);
}
}
}
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 读取 <bean> 标签中的 id 和 name 属性
- 将 Document 中的 Bean 定义转换为 BeanDefinition(实现类为 GenericBeanDefinition)
- 将 BeanDefinition 封装为 BeanDefinitionHolder 并返回
拿到 BeanDefinitionHolder 后,Spring 会将其注册到注册核心。
到这里咱们还是没看到 Spring 是如何解析 <bean> 标签外部的标签的,其实它们是在 parseBeanDefinitionElement 办法中实现的。
留到前面再剖析吧,本文先到这里,切实是太干燥了🤣
小结
本文沿着上篇文章持续跟进,如同没什么本质的货色……算了,就当承前启后吧。
欲知后事如何,且听下回分解。