Spring IoC 源码解析:默认标签的解析过程

上一篇我们梳理了容器初始化的整体流程,了解了一个 bean 是如何从静态配置变为一个可运行的实例的,但是对于过程中涉及到的具体细节并未进行深入探究。从本篇开始,我们将回到起点重新沿着主线走一遍,与之前不同的是,这一次我们更加关注细节。

由前面的分析我们已经大致知晓 IoC 容器在初始化期间主要分为两个阶段:加载并解析配置文件和初始化 bean 实例。本文所要介绍的对于默认标签的解析发生在加载并解析配置文件阶段,以 XML 配置为例,容器会将 XML 形式的静态配置解析成对应的 BeanDefinition 对象注册到容器中。在配置方面,Spring 为开发者提供了许多可用的标签,比如 <beans /><bean /><import />,以及 <alias /> 等等。这些标签统称为 默认标签 (个人觉得翻译成内置标签更加合理),同时 Spring 还允许开发者自己定义标签,方法 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 中的逻辑就是判断当前标签是默认标签还是自定义标签,并调用相应的方法对标签进行解析(实现如下),本篇我们主要分析默认标签的解析过程,对于自定义标签则留到下一篇进行讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)) {
this.parseDefaultElement(ele, delegate);
}
// 解析自定义标签
else {
delegate.parseCustomElement(ele);
}
}
}
}
// 解析自定义标签
else {
delegate.parseCustomElement(root);
}
}

我们在上一篇中已经和上述方法打过照面,如果不清楚该方法的调用时机,可以重新阅读一下。

本文,我们主要关注 DefaultBeanDefinitionDocumentReader#parseDefaultElement 方法。该方法用于对默认标签进行解析,整个方法的逻辑很清晰,判断当前的标签类型,然后调用对应的解析器去做解析处理,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 处理 import 标签,该标签用于引入其它的 xml 配置文件
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
this.importBeanDefinitionResource(ele);
}
// 处理 alias 标签,该标签用于为 bean 配置别名
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
this.processAliasRegistration(ele);
}
// 处理 bean 标签
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
this.processBeanDefinition(ele, delegate);
}
// 处理 beans 标签,即在 <beans /> 中再嵌套 <beans /> 标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
this.doRegisterBeanDefinitions(ele);
}
}

方法按照标签类型分而治之,下面逐个探究各个标签的解析过程。

标签 bean 的解析过程

标签 <bean/> 是基于 XML 进行配置时使用频次最多的标签,所以我们首先从这里开挖。该标签对应的处理方法是 DefaultBeanDefinitionDocumentReader#processBeanDefinition,先来总览一下整个方法的执行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 1. 解析 bean 元素,包括 id、name、alias 和 class
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 2. 如果默认标签下有自定义标签,则进行解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 3. 注册解析得到的 BeanDefinitionHolder
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);
}
/*
* 4. 发出响应事件,通知相关监听器这个 bean 定义已经加载完了
*
* 这里的实现只是为了扩展,Spring 自己并没有对注册实现做任何逻辑处理
*/
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

上述方法的执行流程可以概括为:

  1. 解析各个默认标签元素,将配置语义转换成 BeanDefinition 对象,并记录到 BeanDefinitionHolder 中;
  2. 检查当前标签下是否有自定义标签元素,若存在则进行解析;
  3. 注册由前两步得到的 BeanDefinitionHolder 对象;
  4. 发布事件,通知相应的监听者当前 bean 的默认标签已经解析完成。

解析默认标签元素

默认标签元素的解析位于 DefinitionParserDelegate#parseBeanDefinitionElement 方法中,实现如下:

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
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return this.parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 获取 id 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

// 如果配置了多个 name(以逗号、分号,或者空格分隔),则解析成 List
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}

// 没有配置 id 属性,但配置了至少一个 name,则以第一个 name 作为 id
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
}

// 检查 name 和 alias 的在容器中的唯一性
if (containingBean == null) {
this.checkNameUniqueness(beanName, aliases, ele);
}

// 解析 bean 标签,使用 GenericBeanDefinition 对象封装
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 未设置 beanName,按照规则自动生成一个
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// 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);
}
}
} catch (Exception ex) {
this.error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 使用 BeanDefinitionHolder 对象封装解析后的信息
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

return null;
}

上述解析 <bean /> 标签的执行过程可以概括为:

  1. 获取 id 和 name 属性,并检查属性值在整个配置中的唯一性;
  2. 解析其它属性元素,封装成 GenericBeanDefinition 对象;
  3. Spring 会以 id 或第一个 name 值作为 bean 的唯一标识(即 beanName),如果没有设置则依据命名规则自动生成一个;
  4. 将解析得到的 BeanDefinition 实例、beanName,以及 alias 列表封装成 BeanDefinitionHolder 对象返回。

整个过程中最核心的步骤是第 2 步,这一步完成了由配置到 BeanDefinition 实例的转换,实现如下:

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
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {

// 入栈,用于发生异常时打印异常链
this.parseState.push(new BeanEntry(beanName));

// 获取 class 属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}

// 获取 parent 属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}

try {
// 创建 BeanDefinition 对象,采用 GenericBeanDefinition 实现类
AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);

// 解析 <bean/> 标签的各种属性元素
this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 获取并设置 description 属性
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

// 解析元数据子标签 <meta key="" value=""/>
this.parseMetaElements(ele, bd);
// 解析 lookup-method 子标签
this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析 replaced-method 子标签
this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

// 解析构造方法参数
this.parseConstructorArgElements(ele, bd);
// 解析 property 子标签
this.parsePropertyElements(ele, bd);
// 解析 qualifier 子标签
this.parseQualifierElements(ele, bd);

bd.setResource(this.readerContext.getResource());
bd.setSource(this.extractSource(ele));

return bd;
} catch (ClassNotFoundException ex) {
this.error("Bean class [" + className + "] not found", ele, ex);
} catch (NoClassDefFoundError err) {
this.error("Class that bean class [" + className + "] depends on not found", ele, err);
} catch (Throwable ex) {
this.error("Unexpected failure during bean definition parsing", ele, ex);
} finally {
this.parseState.pop();
}

return null;
}

上述方法会先获取配置的 class 和 parent 属性值,然后基于这两个值创建最初的 BeanDefinition 对象:

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
protected AbstractBeanDefinition createBeanDefinition(
@Nullable String className, @Nullable String parentName) throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}

// org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader)
throws ClassNotFoundException {
// 创建 GenericBeanDefinition 对象
GenericBeanDefinition bd = new GenericBeanDefinition();
// 设置 parent 属性
bd.setParentName(parentName);
// 设置 class 属性
if (className != null) {
// 如果指定了类加载器,则基于此类加载器解析 className 对应的 Class 对象
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
} else {
bd.setBeanClassName(className);
}
}
return bd;
}

由上述实现可以看到,这里创建的 BeanDefinition 对象是 GenericBeanDefinition 类型,并设置对象的 GenericBeanDefinition#parentName 属性。如果指定了类加载器,则基于该类加载器获取 bean 对应的 Class 对象,并设置 AbstractBeanDefinition#beanClass 属性,否则直接将 bean 的 className 记录到该属性上。

解析属性元素

接下来,Spring 会去解析 <bean /> 标签中的各种属性配置,并依据获取到的属性值去设置上面创建的 GenericBeanDefinition 对象。相关实现位于 BeanDefinitionParserDelegate#parseBeanDefinitionAttributes 方法中:

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
88
89
90
91
92
93
94
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele,
String beanName,
@Nullable BeanDefinition containingBean,
AbstractBeanDefinition bd) {

// 属性 singleton 已经过时,推荐使用 scope 属性
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
// 解析并设置 scope 属性
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
// 当前 bean 没有指定 scope,如果传入了containingBean,则继承其scope
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}

// 解析并设置 abstract 属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}

// 解析并设置 lazy-init 属性(false、true、default)
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (this.isDefaultValue(lazyInit)) {
// 如果是默认配置,则继承 default-lazy-init
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

// 解析并设置 autowire 属性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(this.getAutowireMode(autowire));

// 解析并设置 depends-on 属性
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
// 多个 depends 以逗号、分号,或空格分隔
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}

// 解析并设置 autowire-candidate 属性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if (this.isDefaultValue(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
} else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}

// 解析并设置 primary 属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}

// 解析并设置 init-method 属性
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
}
// 尝试使用 default-init-method 配置
else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}

// 解析并设置 destroy-method 属性
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
// 尝试使用 default-destroy-method 配置
else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}

// 解析并设置 factory-method 属性
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}

// 解析并设置 factory-bean 属性
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}

return bd;
}

整个方法解析了 <bean /> 标签所有的属性,包括常用和不常用的,虽然实现很长,但是都是在做一件事情,即判断是否配置了相应的属性,如果配置了则解析并记录到 BeanDefinition 对象中。对于部分具备继承性质的属性,如果没有配置则沿用上游配置的值。下面选择几个不是特别常用的属性举例说明一下。

  • abstract

属性 abstract 与 parent 属性组合使用,让配置具备继承性质。如果多个 bean 在配置上存在大量的重复,这个时候就可以考虑使用继承的配置,抽象出重复的属性配置在父 bean 中,而子 bean 则配置特有的属性,如下:

1
2
3
<bean id="abstractCar" class="org.zhenchao.spring.ioc.Car" abstract="true" p:brand="benz"/>
<bean id="whiteCar" parent="abstractCar" p:color="white"/>
<bean id="blackCar" parent="abstractCar" p:color="black"/>

通过 abstract="true" 属性将父 bean 置为抽象,然后在子 bean 中利用 parent 属性进行引用,这样相同的属性只需要配置一份即可。

  • autowire-candidate

在自动注入时,有时候往往存在多个候选的注入对象,Spring 在无法确定正确的候选者时就会抛出 UnsatisfiedDependencyException 异常,这个时候我们可以将某个或多个候选者配置为 autowire-candidate=false,从而剥夺其候选者的身份。

  • primary

属性 primary 的功能类似于 autowire-candidate,后者是剥夺某个 bean 的自动注入候选者身份,但是如果存在多个候选者时,一个个的配置会比较麻烦,这个时候我们可以使用 primary 属性将某个候选 bean 设置为 primary=true,这样就使得该 bean 在候选时具备了最高优先级。

解析子标签元素

标签 <bean /> 除了提供了大量的属性配置外,还允许我们在该标签中配置子标签,Spring 内置了多种子标签,下面对各个子标签的功能和解析过程逐一分析。

  • 解析 meta 标签

标签 meta 使用的不多,这个配置最终会记录到 BeanDefinition 实例中。假设有如下配置:

1
2
3
<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean">
<meta key="meta_name" value="zhenchao"/>
</bean>

那么我们可以通过如下方式从 bean 对应的 BeanDefinition 实例中获取配置的 meta 值:

1
2
final BeanDefinition definition = beanFactory.getBeanDefinition("myBean");
final Object metaName = definition.getAttribute("meta_name");

通过分析源码我们可以清晰看到 <meta /> 标签解析和记录的过程,位于 BeanDefinitionParserDelegate#parseMetaElements 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
// 获取当前结点下的所有子标签
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <meta/> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
// 获取配置的 key 属性
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
// 获取配置的 value 属性
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
// 使用 BeanMetadataAttribute 对象进行封装
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(this.extractSource(metaElement));
// 记录到 BeanDefinition 的属性集合中
attributeAccessor.addMetadataAttribute(attribute);
}
}
}

由上述实现可以看到,容器是将 <meta /> 标签配置值通过 BeanMetadataAttribute 对象进行封装,并调用 BeanMetadataAttributeAccessor#addMetadataAttribute 方法记录到 attributes 属性中。该属性是一个 map 型的对象,而我们之所以可以从 BeanDefinition 实例中获取到配置值,是因为 BeanDefinition 继承自 BeanMetadataAttributeAccessor 类。

  • 解析 lookup-method 标签

标签 <lookup-method /> 一般用于希望在一个 singleton 对象中获取一个 prototype 对象的场景。假如我们在一个 singleton 对象中寄希望每次调用 getter 方法时获取一个 prototype 类型的新对象,因为外围 bean 实例是 singleton 类型的,其属性也都只有一份,所以不可能每次都返回 prototype 类型属性的新实例,此时我们就可以使用 <lookup-method /> 标签来达到目的。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyBean {

private PrototypeBean prototypeBean;

public PrototypeBean getPrototypeBean() {
return prototypeBean;
}

public void setPrototypeBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
}

MyBean 为 singleton 类型,虽然 PrototypeBean 是 prototype 的,但是我们每次调用 MyBean#getPrototypeBean 方法返回的仍然是同一个对象,这个时候就可以使用 <lookup-method /> 标签:

1
2
3
4
5
<bean id="prototype-bean" class="org.zhenchao.spring.ioc.PrototypeBean" scope="prototype"/>

<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean">
<lookup-method name="getPrototypeBean" bean="prototype-bean"/>
</bean>

标签 <lookup-method />MyBean#getPrototypeBean 方法每次都会去调用一遍 prototype 对象,从而每次都返回新的对象。该标签的解析位于 BeanDefinitionParserDelegate#parseLookupOverrideSubElements 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
// 获取当前结点下的所有子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <lookup-method /> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
// 获取配置的 name 属性
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
// 获取配置的 bean 属性
String beanRef = ele.getAttribute(BEAN_ELEMENT);
// 使用 LookupOverride 对象进行封装
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(this.extractSource(ele));
// 记录到 BeanDefinition 的 overrides 属性中
overrides.addOverride(override);
}
}
}

上述方法的执行逻辑主要是解析 <lookup-method> 标签配置封装成 LookupOverride 对象,并调用 MethodOverrides#addOverride 方法将该对象添加到 overrides 属性中,这是一个线程安全的 Set 集合。实现每次返回一个新对象的机制是通过覆盖目标 bean 的对应方法,在每次调用该方法时都创建一个被引用 bean 的新实例。

  • 解析 replaced-method 标签

标签 <replaced-method /> 顾名思义,是用来替换一个方法的实现。如果希望替换 MyBean 的如下方法:

1
2
3
public void originalMethod() {
System.out.println("This is original method.");
}

我们首先需要定义一个实现了 MethodReplacer 接口的 bean,并实现 MethodReplacer#reimplement 方法:

1
2
3
4
5
6
7
8
9
public class MyMethodReplacer implements MethodReplacer {

@Override
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("This is replaced method.");
return o;
}

}

然后利用 <replaced-method /> 子标签进行配置:

1
2
3
4
5
<bean id="method-replacer" class="org.zhenchao.spring.ioc.MyMethodReplacer"/>

<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean">
<replaced-method name="originalMethod" replacer="method-replacer"/>
</bean>

这样在调用 MyBean#originalMethod 方法时,本质上是在调用 MyMethodReplacer#reimplement 方法。该标签的解析位于 parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) 方法中:

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
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
// 获取当前结点下的所有子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <replaced-method /> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
// 获取配置的 name 属性
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
// 获取配置的 replacer 属性
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
// 使用 ReplaceOverride 对象进行封装
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// 如果配置了 <arg-type /> 子标签,则进行解析
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(this.extractSource(replacedMethodEle));
// 记录到 BeanDefinition 的 overrides 属性中
overrides.addOverride(replaceOverride);
}
}
}

对于 <replaced-method /> 标签的解析类似于 <lookup-method /> 标签,不过标签 <replaced-method /> 在替换方法时可能存在多个方法的重载版本,这个时候就需要通过参数类型进一步确认,所以实现时增加了对于参数类型配置的解析逻辑。

  • 解析构造函数参数标签

构造函数标签 <constructor-arg /> 是我们常用的标签,也是三种依赖注入方式之一。使用示例:

1
2
3
4
<!--参数配置顺序并不能决定在构造方法中的匹配顺序-->
<constructor-arg index="0" name="id" type="long" value="100001"/>
<constructor-arg index="1" name="username" type="java.lang.String" value="zhenchao"/>
<constructor-arg index="2" name="password" type="java.lang.String" value="123456"/>

配置顺序与构造方法中参数定义顺序没有直接关系,所以大部分时候都可能会映射错误,此时我们可以凭借 index 属性和 type 属性从两个维度上做唯一性限制。标签 <constructor-arg /> 可以看作是 <property /> 标签的一种特殊形式,在实现上复用了解析 <property /> 标签的实现,这些复用的实现将留到后面分析 <property /> 标签时再展开。该标签的解析实现位于 BeanDefinitionParserDelegate#parseConstructorArgElements 方法中:

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
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
// 获取当前结点下的所有子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <constructor-arg/> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
this.parseConstructorArgElement((Element) node, bd);
}
}
}

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// 获取 index 属性
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
// 获取 type 属性
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
// 获取 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

// 配置了 index 属性
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
this.error("'index' cannot be lower than 0", ele);
} else {
try {
this.parseState.push(new ConstructorArgumentEntry(index));
// 解析标签元素,复用 <property/> 标签的解析过程
Object value = this.parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
// 设置 type 属性
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
// 设置 name 属性
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(this.extractSource(ele));
// 不允许有重复的 index
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
this.error("Ambiguous constructor-arg entries for index " + index, ele);
} else {
// 记录 index 对应的构造参数 ValueHolder 对象
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
} finally {
this.parseState.pop();
}
}
} catch (NumberFormatException ex) {
this.error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
// 未配置 index 属性
else {
try {
this.parseState.push(new ConstructorArgumentEntry());
// 解析标签元素,复用 <property/> 标签的解析过程
Object value = this.parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
// 设置 type 属性
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
// 设置 name 属性
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(this.extractSource(ele));
// 依据 type 或 name 做参数映射
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
} finally {
this.parseState.pop();
}
}
}

标签 <constructor-arg /> 的解析过程中首先会获取 index、type 和 name 三个属性,然后依据是否设置了 index 属性分成两部分。如果设置了 index 属性,则整个解析过程如下:

  1. 解析 <constructor-arg /> 标签元素,这里复用了 <property /> 标签的解析过程;
  2. 利用 ValueHolder 封装解析出来的元素值,如果设置 type 和 name 属性则记录到到 ValueHolder 对象中;
  3. 检查是否存在重复的 index 配置,没有则以 index 为 key,将 ValueHolder 对象以 Map 的形式记录到 BeanDefinition 实例中。

如果没有设置 index 属性,则整个解析过程如下:

  1. 解析 <constructor-arg /> 标签元素,这里复用了 <property /> 标签的解析过程;
  2. 利用 ValueHolder 封装解析出来的元素值,如果设置 type 和 name 属性则记录到到 ValueHolder 对象中;
  3. 以 type 或 name 去推断当前 ValueHolder 对象所对应的参数,并以 List 的形式记录到 BeanDefinition 实例中。

上述步骤 3 的源码如下:

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
public void addGenericArgumentValue(ValueHolder newValue) {
Assert.notNull(newValue, "ValueHolder must not be null");
if (!this.genericArgumentValues.contains(newValue)) {
this.addOrMergeGenericArgumentValue(newValue);
}
}

private void addOrMergeGenericArgumentValue(ValueHolder newValue) {
if (newValue.getName() != null) {
// 如果在册的参数对象存在与当前相同的参数名称,则尝试 merge 操作
for (Iterator<ValueHolder> it = this.genericArgumentValues.iterator(); it.hasNext(); ) {
ValueHolder currentValue = it.next();
// name 相同
if (newValue.getName().equals(currentValue.getName())) {
if (newValue.getValue() instanceof Mergeable) {
Mergeable mergeable = (Mergeable) newValue.getValue();
if (mergeable.isMergeEnabled()) {
newValue.setValue(mergeable.merge(currentValue.getValue()));
}
}
it.remove();
}
}
}
this.genericArgumentValues.add(newValue);
}

上述方法会去检查当前 ValueHolder 对象是否之前有加载过,没有则判断当前参数是否设置了 name 属性,如果有设置且当前 ValueHolder 对象与已有的 ValueHolder 对象存在相同的 name,则尝试对这个两个对象执行 merge 操作,最后记录 ValueHolder 对象到 ConstructorArgumentValues#genericArgumentValues 属性中,这是一个 List 类型属性。

标签 <constructor-arg /> 的解析过程还是相当复杂的,这里面有相当一部分逻辑复用了 <property /> 标签的解析过程。

  • 解析 property 标签

标签 <property /> 应该是 <bean /> 标签中最常用的子标签,配置方式相当多元化,并且包含丰富的子标签元素。该标签的解析位于 BeanDefinitionParserDelegate#parsePropertyElements 方法中:

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
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
// 获取当前结点下的所有子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <property/> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, PROPERTY_ELEMENT)) {
this.parsePropertyElement((Element) node, bd);
}
}
}

public void parsePropertyElement(Element ele, BeanDefinition bd) {
// 获取 name 属性值
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
this.error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
// name 必须是唯一的
if (bd.getPropertyValues().contains(propertyName)) {
this.error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
// 获取 value 值
Object val = this.parsePropertyValue(ele, bd, propertyName);
PropertyValue pv = new PropertyValue(propertyName, val);
// 解析 <meta/> 子标签
this.parseMetaElements(ele, pv);
pv.setSource(this.extractSource(ele));
// 记录到 BeanDefinition 实例中
bd.getPropertyValues().addPropertyValue(pv);
} finally {
this.parseState.pop();
}
}

上述方法首先会去获取 <property /> 标签的 name 属性,并确保 name 配置在 <bean /> 标签范围内的唯一性;然后调用 BeanDefinitionParserDelegate#parsePropertyValue 方法解析标签值,接着调用 BeanDefinitionParserDelegate#parseMetaElements 方法来解析标签内的 <meta /> 子标签,这个与之前 <bean /> 标签的 <meta /> 子标签的解析过程是一样的,唯一的区别在于这里将最终的解析结果存放到 PropertyValue 实例中,不过最终该实例还是交由 BeanDefinition 实例持有。

下面的示例演示了在 <property /> 标签下嵌入 <meta /> 子标签,假设我们为 MyBean#age 属性配置了 meta:

1
2
3
4
5
<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean">
<property name="age" value="26">
<meta key="age-meta-key" value="age-meta-value"/>
</property>
</bean>

那么我们可以通过如下方式获取到该 meta 值:

1
2
final BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean");
final String metaValue = beanDefinition.getPropertyValues().getPropertyValue("age").getMetadataAttribute("age-meta-key").getValue();

上述过程的核心在于解析 <property /> 标签的 value 配置,即 BeanDefinitionParserDelegate#parsePropertyValue 方法,这也是之前在分析 <constructor-arg /> 标签时留着没有分析的方法,实现如下:

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
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element");

// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 跳过 description 和 meta 属性
if (node instanceof Element && !this.nodeNameEquals(node, DESCRIPTION_ELEMENT) && !this.nodeNameEquals(node, META_ELEMENT)) {
// 一个 <property/> 标签只能对应一种类型:ref, value, list, etc.
if (subElement != null) {
this.error(elementName + " must not contain more than one sub-element", ele);
} else {
subElement = (Element) node;
}
}
}

// 获取 ref 属性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
// 获取 value 属性
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) // 不能既配置了 ref,又配置了 value
|| ((hasRefAttribute || hasValueAttribute) && subElement != null)) // 配置了 ref 或 value 属性,但是又配置了子标签
{
this.error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}

// 获取并解析 ref 属性
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
this.error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(this.extractSource(ele));
return ref;
}
// 获取并解析 value 配置
else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(this.extractSource(ele));
return valueHolder;
}
// 解析子标签
else if (subElement != null) {
return this.parsePropertySubElement(subElement, bd);
}
// 非法配置
else {
// Neither child element nor "ref" or "value" attribute found.
this.error(elementName + " must specify a ref or value", ele);
return null;
}
}

标签 <property /> 的配置方式分为三种:<property name="" value=""/><property name="" ref=""/>,以及 <property name=""></property>,并且同一时刻只能使用其中的一种方式,上述方法也是针对这三种配置分而治之。

如果配置方式是 <property name="" ref=""/>,则获取并解析 ref 属性值封装成 RuntimeBeanReference 对象返回。如果配置方式是 <property name="" value=""/>,则获取并解析 value 值封装成 TypedStringValue 对象返回。而对于 <property name=""></property> 方式来说,因为配置的多元化,Spring 采用专门的方法 BeanDefinitionParserDelegate#parsePropertySubElement 对其进行解析处理:

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
public Object parsePropertySubElement(Element ele, BeanDefinition bd) {
return this.parsePropertySubElement(ele, bd, null);
}

public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
// 自定义标签
if (!this.isDefaultNamespace(ele)) {
return this.parseNestedCustomElement(ele, bd);
}
// 嵌套的 <bean/> 标签
else if (this.nodeNameEquals(ele, BEAN_ELEMENT)) {
BeanDefinitionHolder nestedBd = this.parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
nestedBd = this.decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
}
// 解析 <ref/> 标签
else if (this.nodeNameEquals(ele, REF_ELEMENT)) {
// 获取 bean 属性
String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
boolean toParent = false;
// 未配置 bean 属性,尝试获取并解析 parent 属性
if (!StringUtils.hasLength(refName)) {
// A reference to the id of another bean in a parent context.
refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
toParent = true;
if (!StringUtils.hasLength(refName)) {
this.error("'bean' or 'parent' is required for <ref> element", ele);
return null;
}
}
if (!StringUtils.hasText(refName)) {
this.error("<ref> element contains empty target attribute", ele);
return null;
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
ref.setSource(this.extractSource(ele));
return ref;
}
// 解析 <idref/> 标签
else if (this.nodeNameEquals(ele, IDREF_ELEMENT)) {
return this.parseIdRefElement(ele);
}
// 解析 <value/> 标签
else if (this.nodeNameEquals(ele, VALUE_ELEMENT)) {
return this.parseValueElement(ele, defaultValueType);
}
// 解析 <null/> 标签
else if (this.nodeNameEquals(ele, NULL_ELEMENT)) {
// It's a distinguished null value. Let's wrap it in a TypedStringValue
// object in order to preserve the source location.
TypedStringValue nullHolder = new TypedStringValue(null);
nullHolder.setSource(this.extractSource(ele));
return nullHolder;
}
// 解析 <array/> 标签
else if (this.nodeNameEquals(ele, ARRAY_ELEMENT)) {
return this.parseArrayElement(ele, bd);
}
// 解析 <list/> 标签
else if (this.nodeNameEquals(ele, LIST_ELEMENT)) {
return this.parseListElement(ele, bd);
}
// 解析 <set/> 标签
else if (this.nodeNameEquals(ele, SET_ELEMENT)) {
return this.parseSetElement(ele, bd);
}
// 解析 <map/> 标签
else if (this.nodeNameEquals(ele, MAP_ELEMENT)) {
return this.parseMapElement(ele, bd);
}
// 解析 <props/> 标签
else if (this.nodeNameEquals(ele, PROPS_ELEMENT)) {
return this.parsePropsElement(ele);
}
// 未知的标签
else {
this.error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
return null;
}
}

上述方法的逻辑还是很清楚的,即判断当前标签类型,然后调用对应的解析方法进行针对性的处理。当前标签可以是自定义标签,也可以是默认标签,甚至是两种标签类型的多层嵌套,而 Spring 的实现也是采用方法的嵌套来处理复杂的配置。

对于 <ref /> 标签来说,提供的使用方式有 <ref bean="..." /><ref parent="..." /> 两种类型,这两种配置的区别如下:

  • <ref bean="ref-bean"/> 表示可以引用同一容器或父容器中的 bean,这是最常用的形式。
  • <ref parent="ref-bean"/> 表示引用父容器中的 bean。

对于剩余的标签而言,除了 <idref /><value /><null />,其余的基本都是对集合类型的处理,对应的实现逻辑都比较清晰,这里不再深入。

  • 解析 qualifier 标签

注解 @Qualifier 在日常使用中较为常用,标签 <qualifier /> 的作用与其相同,但是在配置中我们一般较少使用。该标签的解析实现位于 BeanDefinitionParserDelegate#parseQualifierElements 方法中:

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
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
// 获取当前结点下的所有子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 如果是 <qualifier/> 标签则进行解析
if (this.isCandidateElement(node) && this.nodeNameEquals(node, QUALIFIER_ELEMENT)) {
this.parseQualifierElement((Element) node, bd);
}
}
}

public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
// 获取 type 属性
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
if (!StringUtils.hasLength(typeName)) {
this.error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
this.parseState.push(new QualifierEntry(typeName));
try {
// 封装 <qualifier/> 标签配置
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
qualifier.setSource(this.extractSource(ele));
// 获取并设置 value 属性
String value = ele.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(value)) {
qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
}

// 获取并解析 <attribute/> 子标签
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (this.isCandidateElement(node) && this.nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) {
Element attributeEle = (Element) node;
// 获取配置的 key 属性
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
// 获取配置的 value 属性
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
// 封装 <attribute/> 标签配置,并记录到 qualifier 对象中
BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
attribute.setSource(this.extractSource(attributeEle));
qualifier.addMetadataAttribute(attribute);
} else {
this.error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
return;
}
}
}
// 记录 qualifier 对象到 BeanDefinition 中
bd.addQualifier(qualifier);
} finally {
this.parseState.pop();
}
}

上述解析过程的逻辑比较简单,通过 AutowireCandidateQualifier 对象对 <qualifier /> 标签及其子标签进行封装,然后记录到 BeanDefinition 实例中。

至此,默认标签中的默认元素都已经全部解析完成,并设置到 GenericBeanDefinition 对象的相应属性中,该对象会被记录到 BeanDefinitionHolder 对象中返回,最后再回顾一下我们最开始出发的地方:

1
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

解析自定义标签

上一节我们介绍了 Spring 对于默认标签的解析过程,实际上在默认标签作用域内,Spring 还允许我们按照规范自定义标签。在开始分析自定义标签的解析过程之前,我们先来简单介绍一下自定义标签的用法,可能很多人还从来没有自定义过属于自己的标签。

需要注意的一点是,这里我们自定义的标签与下一篇讲解的与默认标签对标的自定义标签并不是同一概念,这里的自定义标签是嵌套在默认标签内的,更准确的说是一种 自定义属性标签 。自定义的过程分为如下几步:

  1. 创建标签实体类;
  2. 定义标签的描述 XSD 文件;
  3. 创建一个标签元素解析器,实现 BeanDefinitionDecorator 接口;
  4. 创建一个 handler 类,继承自 NamespaceHandlerSupport;
  5. 编写 spring.handlers 和 spring.schemas 文件。

下面通过自定义一个简化版的 <property /> 标签演示自定义和使用的过程,该自定义标签包含 name 和 value 两个属性,目的是将 value 值注入到 bean 对应的名为 name 值的属性中。

第一步,先定义一个 <property /> 标签对应的 POJO 类:

1
2
3
4
5
6
7
public class Property {

private String name;
private String value;

// 省略 getter 和 setter
}

第二步,定义标签的 XSD 文件,以约束配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.zhenchao.org/schema/property"
xmlns:tns="http://www.zhenchao.org/schema/property"
elementFormDefault="qualified">
<element name="property">
<complexType>
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="value" type="string"/>
</complexType>
</element>
</schema>

第三步,创建标签元素解析器,这里需要实现 BeanDefinitionDecorator 接口。解析器用于对标签配置属性进行解析,并设置到 BeanDefinition 实例的属性集合中,后续在执行 BeanFactory#getBean 时容器会将属性集合中的值赋值给 bean 实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PropertyBeanDefinitionDecorator implements BeanDefinitionDecorator {

@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
Element element = (Element) node;
if ("mytag:property".equals(element.getNodeName())) {
String name = element.getAttribute("name");
Assert.hasText(name, "The 'name' in 'mytag:property' is missing!");
String value = element.getAttribute("value");
Assert.hasText(value, "The 'value' in 'mytag:property' is missing!");
PropertyValue propertyValue = new PropertyValue(name, value);
definition.getBeanDefinition().getPropertyValues().addPropertyValue(propertyValue);
}
return definition;
}
}

第四步,创建一个继承自 NamespaceHandlerSupport 抽象类的 handler 类,用于注册上面定义的解析器:

1
2
3
4
5
6
7
8
public class PropertyNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
this.registerBeanDefinitionDecorator("property", new PropertyBeanDefinitionDecorator());
}

}

第五步,编写 spring.handlers 和 spring.schemas 文件,放于 META-INF 目录下面,内容如下:

  • spring.handlers
1
http\://www.zhenchao.org/schema/property=org.zhenchao.handler.PropertyNamespaceHandler
  • spring.schemas
1
http\://www.zhenchao.org/schema/property.xsd=META-INF/property.xsd

下面来看一下如何使用。首先需要在头部定义标签命名空间:

1
2
3
4
5
6
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mytag="http://www.zhenchao.org/schema/property"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.zhenchao.org/schema/property http://www.zhenchao.org/schema/property.xsd"

假设 MyBean 中有一个 tag 属性,那么我们可以利用我们自定义的标签进行如下配置:

1
2
3
<bean id="myBean" class="org.zhenchao.spring.ioc.MyBean">
<mytag:property name="tag" value="myCustomTagValue"/>
</bean>

当我们获取 MyBean 实例的 tag 属性值时,就能够得到我们配置的值,类似于标准 <property /> 标签的效果,不过 Spring 提供的 <property /> 标签功能要强大很多,这里只是演示如何自定义一个属于自己的标签,实际开发中我们自定义的标签也会比这要复杂的多。当我们在使用 Spring 默认的标签配置时,如果发现配置异常复杂,这个时候就可以考虑是否可以通过自定义标签来简化配置。不过笔者也不推荐为了自定义而自定义,自定义的标签给后来人阅读源码带来了很大的负担,增加了学习成本。

介绍完了自定义标签的定义和使用方式,我们继续来剖析 Spring 对于自定义标签解析过程的实现。自定义标签的解析过程位于 BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired 方法中:

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
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
return this.decorateBeanDefinitionIfRequired(ele, originalDef, null);
}

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

BeanDefinitionHolder finalDefinition = originalDef;

// Decorate based on custom attributes first.
// 1. 获取并处理当前标签的属性集合
NamedNodeMap attributes = ele.getAttributes();
// 遍历所有的属性,如果是目标自定义标签则进行处理
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}

// Decorate based on custom nested elements.
// 2. 获取并处理当前标签的子标签集合
NodeList children = ele.getChildNodes();
// 遍历所有的子标签,如果是目标自定义标签则进行处理
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}

上述方法的实现逻辑分为两步执行:第一步处理当前标签所有的自定义属性;第二步处理当前标签的所有自定义子标签。不过,这两步最终都是通过调用 BeanDefinitionParserDelegate#decorateIfRequired 方法完成处理,实现如下:

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
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

// 获取自定义标签的命名空间
String namespaceUri = this.getNamespaceURI(node);
if (namespaceUri != null && !this.isDefaultNamespace(namespaceUri)) {
// 根据命名空间找到对应的 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
// 调用 decorate 方法处理自定义标签
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {
return decorated;
}
} else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
} else {
// A custom namespace, not to be handled by Spring - maybe "xml:...".
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}

解析过程与我们定义自定义标签的过程相呼应。首先获取标签的命名空间,并以此来判断当前属性或标签是否是自定义的,如果是则获取对应的 NamespaceHandler 类对象,也就是我们之前自定义的 PropertyNamespaceHandler 类,我们在该类的 PropertyNamespaceHandler#init 方法中注册了我们的 BeanDefinitionDecorator 实例:

1
2
3
public void init() {
this.registerBeanDefinitionDecorator("property", new PropertyBeanDefinitionDecorator());
}

然后调用 handler 处理自定义的标签,这里本质上还是调用了我们自定义实现的 PropertyBeanDefinitionDecorator#decorate 方法,这样就将我们自定义的实现和 Spring 框架集成在了一起。

注册 BeanDefinition 对象

在将 bean 的默认标签和自定义标签都设置到 BeanDefinition 实例中后,接下来就是向 IoC 容器注册 BeanDefinition 对象啦。Spring 定义了 BeanDefinitionHolder 类用于封装 BeanDefinition 对象,以及 bean 对应的唯一 name 和 alias 列表,并将 BeanDefinition 以 BeanDefinitionHolder 对象的形式注册到容器中。BeanDefinitionHolder 类的定义如下:

1
2
3
4
5
6
7
8
public class BeanDefinitionHolder implements BeanMetadataElement {

private final BeanDefinition beanDefinition;
private final String beanName;
private final String[] aliases;

// ... 省略方法
}

往容器注册 BeanDefinitionHolder 对象的实现位于工具方法 BeanDefinitionReaderUtils#registerBeanDefinition 中,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {

// 以 beanName 作为唯一标识进行注册
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// 建立 alias 与 beanName 之间的映射关系
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

整个注册过程主要做了两件事情:

  1. 以唯一的 beanName 作为 key,注册 BeanDefinition 实例,本质上就是以 map 进行内存存储;
  2. 建立 alias 与 beanName 之间的映射关系。

下面分别对这两个过程展开分析。

往容器注册 BeanDefinition 对象

注册 BeanDefinition 实例由 DefaultListableBeanFactory#registerBeanDefinition 方法实现,如下:

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
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {
try {
/*
* 校验 AbstractBeanDefinition 的 methodOverrides 属性,
* 验证 methodOverrides 是否与工厂方法并存或覆盖的方法根本不存在
*/
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(
beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
}
}

// 尝试获取 beanName 已绑定的 BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 当前 beanName 已经绑定了某个 BeanDefinition 实例
if (existingDefinition != null) {
// 不允许覆盖绑定,则抛出异常
if (!this.isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
// 覆盖后的 bean 的应用场景变小
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
/*
* role 用于定义 bean 的应用场景:
* ROLE_APPLICATION:值为 0,用户
* ROLE_SUPPORT:值为 1,某些复杂配置的一部分
* ROLE_INFRASTRUCTURE:值为 2,完全内部使用
*/
if (logger.isInfoEnabled()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
}
// 注册到容器中,本质上用 map 保存注册信息
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 当前 beanName 未绑定任何 BeanDefinition 实例
else {
if (this.hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
this.removeManualSingletonName(beanName);
}
} else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}

// 清空当前 beanName 对应的 BeanDefinition 缓存
if (existingDefinition != null || this.containsSingleton(beanName)) {
this.resetBeanDefinition(beanName);
}
}

往容器注册 BeanDefinition 时首先会判断 BeanDefinition 是否是 AbstractBeanDefinition 类型实例,如果是则进一步验证其 methodOverrides 属性,防止出现与工厂方法并存或覆盖的方法根本不存在的情况。然后检查 beanName 是否已经绑定了 BeanDefinition 实例,如果已经绑定且允许覆盖已有的实例,则执行覆盖操作,如果没有绑定则直接注册。注册操作本质上是以 beanName 作为 key,以 BeanDefinition 实例作为 value 记录到 map 数据结构中,后续加载 bean 时会从依据给定的 beanName 从中获取 BeanDefinition,并依据 BeanDefinition 创建 bean 对象。

建立 alias 到 beanName 之间的映射

往容器注册 BeanDefinition 的第二步是建立 alias 与 beanName 之间的映射关系。如果把 BeanDefinition 实例看做是文件的话,那么 beanName 可以看作是文件的硬链接,而 alias 则可以看作是软连接,是 beanName 的快捷方式。建立映射的过程由 SimpleAliasRegistry#registerAlias 方法实现:

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
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {
// alias 与 name 相同,则将 alias 从映射关系中删除
if (alias.equals(name)) {
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
}
// alias 与 name 不同,建立映射关系
else {
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
// 已经建立了正确的映射,直接返回
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
// 已经与其它 beanName 建立了映射,且不允许覆盖
if (!this.allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
}
// 检测 name 和 alias 之间是否构成环路,如果构成环路则抛出异常
this.checkForAliasCircle(name, alias);
// 不存在环路,建立 alias 与 name 之间的映射关系
this.aliasMap.put(alias, name);
}
}
}

上述实现比较简单,如代码注释,不再多做撰述。

发布事件通知

至此,Spring 完成了对 <bean /> 标签的解析过程,将 <bean /> 标签配置转换成 BeanDefinition 对象注册到 IoC 容器中。考虑到一些应用可能需要感知这一事件,Spring 在完成对一个 <bean /> 标签的解析之后会发布事件通知,通过调用 ReaderContext#fireComponentRegistered 方法将消息通知到具体的监听者。如果用户希望监听这一事件,可以实现 ReaderEventListener 接口,Spring 会在发布事件通知时回调 ReaderEventListener#componentRegistered 方法。

标签 import 的解析过程

标签 <import /> 也是我们比较常用的标签,尤其是在大型项目中,通过将各个模块的 Spring 配置分开定义,并在需要的地方通过 <import /> 标签引入,可以让配置更加的清晰,易于管理。该标签的解析过程位于 DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource 方法中:

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
protected void importBeanDefinitionResource(Element ele) {
// 获取 resource 属性,例如 <import resource="xx.xml"/>
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
if (!StringUtils.hasText(location)) {
this.getReaderContext().error("Resource location must not be empty", ele);
return;
}

// 解析路径中的系统属性,比如可能存在如 ${user.dir} 的占位符
location = this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

Set<Resource> actualResources = new LinkedHashSet<>(4);

// 检测是绝对路径,还是相对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}

// 绝对路径
if (absoluteLocation) {
try {
// 加载 bean 定义,并返回加载的数目
int importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
// 相对路径
else {
// No URL -> considering resource location as relative to the current file.
try {
int importCount;
// Resource 存在多个子类,各子类的 createRelative 实现不一样,这里先使用子类的方法尝试解析
Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 加载 bean 定义,并返回加载的数目
importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
// 解析不成功,使用默认的解析器 ResourcePatternResolver 进行解析
String baseLocation = this.getReaderContext().getResource().getURL().toString();
importCount = this.getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException ex) {
this.getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[0]);
// 解析完成之后,发布事件通知
this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));
}

标签 <import /> 仅包含一个 resource 属性,该属性指定了配置文件的路径。路径可以是相对路径,也可以是绝对路径,路径中还可能存在一些系统属性占位符,比如 ${user.dir}。上述方法首先对系统属性进行了处理,然后判断当前路径属于绝对路径还是相对路径,并分而治之。

标签 alias 的解析过程

标签 <alias /> 用于为一个已定义的 bean 设置别名,虽然在 <bean /> 标签中可以通过 name 属性定义别名,但是存在即合理,标签 <alias/> 总有它的应用场景。针对该标签的解析由 DefaultBeanDefinitionDocumentReader#processAliasRegistration 方法实现:

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
protected void processAliasRegistration(Element ele) {
// 获取 name 属性
String name = ele.getAttribute(NAME_ATTRIBUTE);
// 获取 alias 属性
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
// name 不允许为空
if (!StringUtils.hasText(name)) {
this.getReaderContext().error("Name must not be empty", ele);
valid = false;
}
// alias 不允许为空
if (!StringUtils.hasText(alias)) {
this.getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
// 注册 alias
this.getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex);
}
// 注册完成,发布事件通知
this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele));
}
}

标签 <alias/> 的解析过程复用了上面介绍的 SimpleAliasRegistry#registerAlias 方法,用于建立 alias 与 beanName 之间的映射关系,同时避免出现环路。

标签 beans 的解析过程

上面介绍的几种标签都是位于 <beans /> 标签下面,本小节将要分析的 <beans /> 标签是嵌套在外围 <beans /> 标签中的,本质上没有什么区别,所以 Spring 的解析过程也是递归调用了之前的解析过程,实现如下:

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
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 = this.createDelegate(this.getReaderContext(), root, parent);

// 处理 profile 标签(其作用类比 pom.xml 中的 profile)
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 (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}

// 模板方法,预处理
this.preProcessXml(root);
// 解析并注册 BeanDefinition
this.parseBeanDefinitions(root, this.delegate);
// 模板方法,后处理
this.postProcessXml(root);

this.delegate = parent;
}

上述方法用于解析 <beans /> 标签。慢着!是不是看着有点眼熟,没错,本文最开始就是从这个方法中的 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 方法开挖的。饶了一大圈,我们又回到了起点,突然想到了一部电影 《恐怖游轮》,不多说了,睡觉~

参考

  1. Spring 源码深度解析