Spring 源码阅读 08:加载 BeanDefinition 的过程(解析阶段)

上一篇提到了XmlBeanDefinitionReader类的doLoadBeanDefinitions方法中有两行关键的代码:

Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);

第一行是将 XML 配置文件资源加载并解析成 Document 对象,这是 XML 文件解析的过程,是上一篇的主要内容。第二行是注册 BeanDefinition 的过程。

BeanDefinition 的注册其实可以分为两个部分,分别是把 Document 对象中的内容解析成 BeanDefinition 的过程,和把 BeanDefinition 注册到容器中的过程。

这篇接着之前的内容,分析 XML 资源加载成 Document 对象之后,是如何被解析成 BeanDefinition 的。

##BeanDefinition 的解析
下面从int count = registerBeanDefinitions(doc, resource)这行代码入手,先找到registerBeanDefinitions方法的源码:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

下面罗列一下方法中的逻辑:

1.创建一个 BeanDefinitionDocumentReader 对象documentReader
2.获取 Spring 容器中已经注册的 BeanDefinition 的数量
3.调用documentReader的registerBeanDefinitions方法,初步分析这里完成了解析和向容器中注册 BeanDefinition 的过程。
4.通过简单运算得到本次注册的 BeanDefinition 的数量并返回。

这里主要关注一下步骤1和3。

##documentReader的创建
首先简单看一下documentReader是如何创建的:

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
   return BeanUtils.instantiateClass(this.documentReaderClass);
}
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
      DefaultBeanDefinitionDocumentReader.class;

这里从createBeanDefinitionDocumentReader方法和documentReaderClass成员变量的代码可以看出,这里通过反射创建了一个 DefaultBeanDefinitionDocumentReader 类型的对象。

下面再看最重要的registerBeanDefinitions方法:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

##BeanDefinitionParserDelegate的创建
这里看到了一个名为doXXX的方法调用,应该就是这部分的核心逻辑了,这里传入的参数是doc.getDocumentElement(),代表的是 XML 文件中的beans标签。查看方法源码:

 /**
* Register each bean definition within the given root {  @code  <beans/>} element.
*/
@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
   // Any nested <beans> elements will cause recursion in this method. In
   // order to propagate and preserve <beans> default-* attributes correctly,
   // keep track of the current (parent) delegate, which may be null. Create
   // the new (child) delegate with a reference to the parent for fallback purposes,
   // then ultimately reset this.delegate back to its original (parent) reference.
   // this behavior emulates a stack of delegates without actually necessitating one.
   BeanDefinitionParserDelegate parent = this.delegate;
   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);
         // We cannot use Profiles.of(...) since profile expressions are not supported
         // in XML config. See SPR-12458 for details.
         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;
         }
      }
   }

   preProcessXml(root);
   parseBeanDefinitions(root, this.delegate);
   postProcessXml(root);

   this.delegate = parent;
}

这里先解释一下方法体中第一行和最后一行代码,这两行代码看起来是在开头将this.delegate成员变量赋值给了一个局部变量,在方法末尾在还原,在中间部分,会创建新的delegate来执行真正的工作。这么做的原因是,如果在配置文件中,一个beans标签中又嵌套了另一个beans标签,那么会递归调用这个方法,这两句代码其实是在处理每一层递归栈各自的状态。

在上面的方法源码中,先看delegate的创建:

protected BeanDefinitionParserDelegate createDelegate(
      XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

   BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
   delegate.initDefaults(root, parentDelegate);
   return delegate;
}

这里先知道delegate是 BeanDefinitionParserDelegate 类型的,从名字可以初步推断出,它是一个从 Document 中解析 BeanDefinition 的代理类,后面具体解析的时候,我们再做分析。

这个方法中最核心的部分是后面的三行代码:

preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

其中,preProcessXml和postProcessXml两个方法的方法体是空的,且方法是protected修饰的,因此可以推断,这两个方法是留给子类的扩展点。

##匹配标签解析的逻辑

下面重点看parseBeanDefinitions方法:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   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);
   }
}

从这里的代码可以大概看出,要真正开始对 Document 的内容进行解析了。

这里先简单介绍一下 Node 和 Element。Element 是 XML 中的一个标签,而 Node 是 XML 中的一个节点,节点可以是标签,也可以是标签的属性或者标签体中的文本内容。
接下来看第一行if判断语句delegate.isDefaultNamespace(root),判断一个元素是不是默认命名空间,先找到这个方法的具体内容:

public boolean isDefaultNamespace(Node node) {
   return isDefaultNamespace(getNamespaceURI(node));
}
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
   return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

根据以上的三段代码分析,这里的逻辑是,获取到元素的namespaceUri如果它是空的,或者是http://www.springframework.org/schema/beans,那么它就是默认命名空间,也就是默认标签。

回到上面的parseBeanDefinitions方法,我们可以总结这段逻辑:

判断当前处理的是不是默认标签,如果不是,就执行自定义标签的解析逻辑
遍历当前标签的所有子节点
如果子节点也是标签的话,判断它是不是默认标签,如果是就执行默认标签的解析逻辑,如果不是则执行自定义标签的解析逻辑

这里的默认标签指的是beans、bean等标签,除了默认标签以外,还有很多你一定见过的自定义标签,比如context:component-scan/等。之后的分析只考虑分析默认标签的情况。

在进入到默认标签处理逻辑parseDefaultElement方法中:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}

来到这里以后,会根据标签的名称来执行不同的解析逻辑。
如果是beans标签,那么又会执行前面介绍过的doRegisterBeanDefinitions方法,这也是前面提到这个方法可能会递归调用的原因。不过根据前面的代码分析,我们这里要解析的是bean标签的内容,因此会进入到processBeanDefinition(ele, delegate);这个方法调用中。
bean标签元素信息的解析
我们继续深入代码:
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));
}
}
复制代码
这里可以看到,最终解析的工作还是delegate来完成的,解析的结果是一个 BeanDefinitionHolder 的对象bdHolder,根据名称推断,这个对象会持有解析出的 BeanDefinition。之后,在try语句块中,将解析的结果注册到容器中。
注册的具体过程我们放到下一篇,接下来看解析的过程。进入delegate的parseBeanDefinitionElement方法:
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
复制代码
调用重载方法:
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 第1部分
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

// 第2部分
List aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}

// 第3部分
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace(“No XML ‘id’ specified - using '” + beanName +
“’ as bean name and " + aliases + " as aliases”);
}
}

if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}

// 第4部分
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
/* 省略,因为我们目前分析的流程中,beanName 不可能为空 */
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

return null;
}
复制代码
这里我通过注释将代码分成了4部分,下面分别来介绍这4部分的逻辑:

从 XML 标签中获取其中的id和name属性。
处理别名,因为 Spring 允许给 Bean 配置多个名称,这一步就是解析出这些名称,放到aliases数组中。
将id作为 Bean 在容器中的唯一名称,如果没有给 Bean 配置id,则从aliases中取出第一个作为 Bean 的名称,并检查名称的唯一性。
解析 Bean 对应的标签,并得到 AbstractBeanDefinition 类型的结果,如果结果不为空,则封装 BeanDefinitionHolder 对象并返回。

bean标签元素内容的解析
接下来,在进入到parseBeanDefinitionElement方法中,查看 Bean 对应的标签解析的过程:
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {

this.parseState.push(new BeanEntry(beanName));

String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}

try {
AbstractBeanDefinition bd = createBeanDefinition(className, parent);

  parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

  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;

}
/* 省略异常处理逻辑 */
finally {
this.parseState.pop();
}

return null;
}
复制代码
这里的主要流程在try语句块中,我分别来介绍一下这些代码都干了什么:

第一行代码中,通过createBeanDefinition方法创建了一个AbstractBeanDefinition。
接下来的两行代码,解析了bean标签中的各种属性,并封装到了bd中。(其中的代码比较长且逻辑简单,感兴趣的话可以自行查看)
接下来的6行代码,分别解析了bean的各种子标签的信息,并封装到bd中,这些子标签包括meta、lookup-method、replaced-method、constructor-arg、property、qualifier,关于这些子标签的信息不在本文讨论范围,不过你应该对它们都很熟悉。
最后添加了一些附加信息之后将bd返回。

BeanDefinition 的创建
这里我们再看一下createBeanDefinition方法是如何创建 BeanDefinition 的:
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}
复制代码
这里在继续查看BeanDefinitionReaderUtils.createBeanDefinition的源码:
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
else {
bd.setBeanClassName(className);
}
}
return bd;
}
复制代码
这里可以看到,我们实际创建的 BeanDefinition 的类型是 GenericBeanDefinition。
后续
至此,将 Document 解析成 BeanDefinition 的过程,就分析到这里,对于bean标签的子标签的解析过程,将不再分析,因为其中的内容比较多,且逻辑简单,其核心任务都是将标签中解析到的信息封装到 BeanDefinition 当中。至此我们已经了解了整个过程的逻辑,想了解更细节的解析过程,可以自己继续深入阅读代码。
下一篇讲分析 Spring 是如何将 BeanDefinition 注册到容器中的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值