这一章开始真正解析xml的标签,逻辑不复杂,但是内容比较多,所有单独放一篇文章。继续上一章的BeanDefinitionDocumentReader.registerBeanDefinitions(),该接口只有一个实现类就是:DefaultBeanDefinitionDocumentReader。以下面xml为例

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

<import resource="classpath*:/spring/job-timer.xml" />

<bean id="some" class="src.com.Some"/>
<alias name="some" alias="someJava,oneBean,twoBean"/>
</beans>

区分xml结构定义

registerBeanDefinitions()处理逻辑从xml的根节点开始。调用doRegisterBeanDefinitions(),判断xml是否为spring所定义的xml结构,分别处理。

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
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 获取根节点标签 <beans>
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}

// 处理传入的节点标签下的子节点标签,存在递归调用
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

// 处理使用spring默认的xml结构定义
// 例如:例子中http://www.springframework.org/schema/beans
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.isInfoEnabled()) {
logger.info("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;
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 处理使用spring默认的xml结构定义
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 {
// 如果没有使用spring默认的xml结构定义,则使用用户自定义的结构,进行解析
delegate.parseCustomElement(ele);
}
}
}
} else {
// 根节点没有使用spring的定义的xml结构
// 使用自定义的解析规则解析根节点
delegate.parseCustomElement(root);
}
}

从上面代码看出,xml的文档接口,可以有我们自定义,也就是我们所说的DTD。spring默认的结构定义使用XSD,是基于 XML 的 DTD 替代者。

使用spring的xml结构解析

对xml结构解析是从parseDefaultElement()开始的,该方法分别对import、alias、bean、beans标签进行路由调用指定的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 解析 <import> 标签
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 解析 <alias> 标签
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 解析 <bean> 标签
processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 递归调用,本篇开始的方法
doRegisterBeanDefinitions(ele);
}
}

解析 import 标签

importBeanDefinitionResource(),先获得resource属性的值,也就是需要导入的资源路径。判断该路径是否为绝对路径还是相对路径分别处理。逻辑跟ioc启动时传入的location地址处理方式大相径庭,先解析地址获得Resource对象,然后继续往下解析。

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
protected void importBeanDefinitionResource(Element ele) {
// 获取resource参数的值,也就是要导入的文件路径
// 例如:例子中的classpath*:/spring/job-timer.xml
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 非空判断,不做处理
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}

// 使用系统变量值:"${user.dir}"解析location值
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

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

// 标记location值是否为绝对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
// 不能转为URI对象,则为相对地址
}

if (absoluteLocation) {
// 为绝对地址
try {
// 加载给定文件的bean资源。里面逻辑还是一样的,先获取到Resource对象,然后继续加载
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
} else {
// 相对路径处理逻辑
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 相对路径地址存在,继续加载Resource
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
// 相对路径不存在,则使用ioc资源读取器的基本路径
String baseLocation = getReaderContext().getResource().getURL().toString();
// 使用ioc读取器的基本路径加载给定的路径资源
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
// 解析完后,发送“导入其他资源”处理完成的通知事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

解析 alias 标签

processAliasRegistration()先获取到name、alias属性的值,非空处理之后,调用registerAlias()向IOC中注册别名。registerAlias()的逻辑为:将别名保存到Map中,由alias映射到name。

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
protected void processAliasRegistration(Element ele) {
// 获得name属性、alias属性
// 例如:例子中 name="some" alias="someJava,oneBean,twoBean"
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
// 非空处理,做标记
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
// 向容器中注册别名
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
// 发送别名处理完的事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}

// SimpleAliasRegistry
public void registerAlias(String name, String alias) {
// 非空处理
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
// 如果aliasname一致,相当于没有取别名。
// remove已注册的别名,节省别名映射的性能
if (alias.equals(name)) {
this.aliasMap.remove(alias);
} else {
String registeredName = this.aliasMap.get(alias);
// 如果已注册该别名,则判断之前注册的和本次注册的是否指向同一个bean。否则抛异常
// 之所以要判断之前注册的和本次注册的是否指向同一个bean,是因为在最后注册BeanDefinition要兼容其他的IOC容器,也会调用registerAlias()
if (registeredName != null) {
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
}
checkForAliasCircle(name, alias);
// 注册别名
this.aliasMap.put(alias, name);
}
}

解析 bean 标签

processBeanDefinition()先将bean标签解析成BeanDefinitionHolder,再将BeanDefinitionHolder注册到IOC中。而BeanDefinitionHolder是对BeanDefinition的封装。如果说BeanDefinition是bean的描述,那么BeanDefinitionHolder则是对这个描述取一个名字,告诉这个BeanDefinition属于谁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 对bean标签进行解析
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 这里是将BeanDefinition注册到IOC的入口
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 发送BeanDefinition注册完成的事件通知
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

其中最重要的两个方法parseBeanDefinitionElement()和registerBeanDefinition(),这里不方便细讲,bean标签里面的子标签、属性太多,留给一下章细说。