【Spring源码】IoC之解析<bean>标签:开启解析进程

import 标签解析完毕了,我们一起来看看 Spring 中最复杂也是最重要的标签 bean 标签的解析过程。

processBeanDefinition

在方法 #parseDefaultElement(...) 方法中,如果遇到标签为 bean 时,则调用 #processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法,进行 bean 标签的解析。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// DefaultBeanDefinitionDocumentReader.java

/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 进行 bean 元素解析。
// <1> 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
// 如果解析失败,则返回 null 。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// <2> 进行自定义标签处理
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// <3> 进行 BeanDefinition 的注册
// 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);
}
// <4> 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

整个过程分为四个步骤:

  1. 调用 BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate)方法,进行元素解析。
    • 如果解析失败,则返回 null,错误由 ProblemReporter 处理。
    • 如果解析成功,则返回 BeanDefinitionHolder 实例 bdHolder 。BeanDefinitionHolder 为持有 namealias 的 BeanDefinition。
    • 详细解析,见下文。
  2. 若实例 bdHolder 不为空,则调用 BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder bdHolder) 方法,进行自定义标签处理。
  3. 解析完成后,则调用 BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法,对 bdHolder 进行 BeanDefinition 的注册。
  4. 发出响应事件,通知相关的监听器,完成 Bean 标签解析。

parseBeanDefinitionElement

BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,进行 <bean> 元素解析。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// BeanDefinitionParserDelegate.java

/**
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}

/**
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*
* @param containingBean TODO 芋艿,需要进一步确认
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// <1> 解析 id 和 name 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

// <1> 计算别名集合
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}

// <3.1> beanName ,优先,使用 id
String beanName = id;
// <3.2> beanName ,其次,使用 aliases 的第一个
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");
}
}

// <2> 检查 beanName 的唯一性
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}

// <4> 解析属性,构造 AbstractBeanDefinition 对象
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// <3.3> beanName ,再次,使用 beanName 生成规则
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
// <3.3> 生成唯一的 beanName
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
// <3.3> 生成唯一的 beanName
beanName = this.readerContext.generateBeanName(beanDefinition);
// TODO 芋艿,需要进一步确认
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
} catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
// <5> 创建 BeanDefinitionHolder 对象
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}

这个方法还没有对 bean 标签进行解析,只是在解析动作之前做了一些功能架构,主要的工作有:

  • <1> 处,解析 idname 属性,确定 aliases 集合

  • <2> 处,检测 beanName 是否唯一。代码如下:

  • /**
     * 已使用 Bean 名字的集合
     *
     * Stores all used bean names so we can enforce uniqueness on a per
     * beans-element basis. Duplicate bean ids/names may not exist within the
     * same level of beans element nesting, but may be duplicated across levels.
     */
    private final Set<String> usedNames = new HashSet<>();
    
    /**
     * Validate that the specified bean name and aliases have not been used already
     * within the current level of beans element nesting.
     */
    protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
        // 寻找是否 beanName 已经使用
        String foundName = null;
        if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
            foundName = beanName;
        }
        if (foundName == null) {
            foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
        }
        // 若已使用,使用 problemReporter 提示错误
        if (foundName != null) {
            error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
        }
    
        // 添加到 usedNames 集合
        this.usedNames.add(beanName);
        this.usedNames.addAll(aliases);
    }
  • 这里有必要说下 beanName 的命名规则:

【Spring源码】IoC之解析Bean:解析import标签

【Spring源码】IoC之注册BeanDefinitions 提到,Spring 有两种解析 Bean 的方式:

  • 如果根节点或者子节点采用默认命名空间的话,则调用 #parseDefaultElement(...) 方法,进行默认标签解析
  • 否则,调用 BeanDefinitionParserDelegate#parseCustomElement(...) 方法,进行自定义解析。

本文就对这两个方法进行详细分析说明。

阅读更多

【Spring源码】IoC之注册BeanDefinitions

【Spring源码】IoC之加载 BeanDefinition中提到,在核心逻辑方法#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中,中主要是做三件事情:

  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式。
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。
  3. 调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息

这篇博客主要第 3 步,分析注册 Bean 信息。

阅读更多

【Spring源码】IoC之获取Document对象

【Spring源码】IoC之加载 BeanDefinition中提到,在核心逻辑方法#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中,中主要是做三件事情:

  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式。
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例
  3. 调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。

这篇博客主要第 2 步,分析获取 XML Document 对象。

阅读更多

【Spring源码】IoC之获取验证模型

在上篇【Spring源码】IoC之加载 BeanDefinition中提到,在核心逻辑方法#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中,中主要是做三件事情:

  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。
  3. 调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。

这篇博客主要第 1 步,分析获取 xml 文件的验证模式。

阅读更多

【Spring源码】IoC之Spring统一资源加载策略

在学 Java SE 的时候,我们学习了一个标准类java.net.URL,该类在 Java SE中定位为统一资源定位器(Uniform Resource Locator),但是我们知道它的实现基本只限于网络形式发布的资源的查找和定位。然而,实际上资源的定义比较广泛,除了网络形式的资源,还有以二进制形式存在的、以文件形式存在的、以字节流形式存在的等等。而且它可以存在于任何场所,比如网络、文件系统、应用程序中。所以java.net.URL的局限性迫使 Spring 必须实现自己的资源加载策略,该资源加载策略需要满足如下要求:

  1. 职能划分清楚。资源的定义和资源的加载应该要有一个清晰地界限
  2. 统一的抽象。统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。
阅读更多

【Spring源码】深入理解Spring IoC

一开始学习Spring的时候就接触IoC了,作为Spring第一个最核心的概念,我们在解读它的源码之前必须要对其有深入的认识,本篇主要介绍IoC基本概念和各个组件。

本文主要基于 Spring 5.0.6.RELEASE

阅读更多