七、解析import标签

解析import标签

在注册BeanDefinition中知道解析Bean有两种方式:

  • 默认解析方式parseDefaultElement()
  • 自定义解析方式parseCustomElement()

默认解析过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String IMPORT_ELEMENT = "import";
//"bean"
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String NESTED_BEANS_ELEMENT = "beans";

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);
}
}

import示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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">

<context:property-placeholder location="classpath:application.properties"/>
<import resource="spring-${suffix1}.xml"/>
<import resource="spring-${suffix2}.xml"/>
</beans>

//application.properties内容
suffix1 = name1
suffix2 = name2

一个applicationContext.xml配置文件中可以使用import标签导入其他模块的配置文件。维护成本较低。

importBeanDefinitionResource()方法

DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(),如方法注释:解析import标签,并将给定资源中的bean加载到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
protected void importBeanDefinitionResource(Element ele) {
// <1> 获取 resource 的属性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 为空,直接退出
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// <2> 解析系统属性,格式如 :"${user.dir}"
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
// 实际 Resource 集合,即 import 的地址,有哪些 Resource 资源
Set<Resource> actualResources = new LinkedHashSet<>(4);
//location是绝对路径还是相对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
//相对位置不能转换为URI除非有一个默认的前缀"classpat*:"
}
// Absolute or relative?
if (absoluteLocation) {
try {
// 添加配置文件地址的 Resource 到 actualResources 中,并加载相应的 BeanDefinition
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isTraceEnabled()) {
logger.trace("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 {
// No URL -> considering resource location as relative to the current file.
try {
int importCount;
// 创建相对地址的 Resource
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 加载 relativeResource 中的 BeanDefinition
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
// 添加到 actualResources 中
actualResources.add(relativeResource);
}
else {
// 获得根路径地址
String baseLocation = getReaderContext().getResource().getURL().toString();
// 添加配置文件地址的 Resource 到 actualResources 中,并加载相应的 BeanDefinition
//和上边绝对路径的添加方式一样,不过多了一步获取绝对路径
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isTraceEnabled()) {
logger.trace("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);
}
}
// <6> 解析成功后,进行监听器激活处理
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
判断路径

判断location是相对路径还是绝对路径:

1
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
ResourcePatternUtils.isUrl(location)

classpath*: 或者 classpath: 开头的或者通过resourceLocation创建的java.net.URL为绝对路径。

方法具体内容:

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
//ResourcePatternUtils.java
public static boolean isUrl(@Nullable String resourceLocation) {
return (resourceLocation != null &&
(
//"classpath*:"开头,绝对路径
resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX) ||
//
ResourceUtils.isUrl(resourceLocation)
)
);
}

//ResourceUtils.java
public static boolean isUrl(@Nullable String resourceLocation) {
if (resourceLocation == null) {
return false;
}
//"classpath:"开头,绝对路径
if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
return true;
}
try {
//通过`resourceLocation`创建的"java.net.URL",绝对路径
new URL(resourceLocation);
return true;
}
catch (MalformedURLException ex) {
return false;
}
}

//ResourceUtils.java
public static URI toURI(String location) throws URISyntaxException {
return new URI(StringUtils.replace(location, " ", "%20"));
}
//URI.java
public boolean isAbsolute() {
return scheme != null;
}
处理绝对路径

如果是绝对路径,执行int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);,该方法定义在AbstractBeanDefinitionReader中。

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
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获得 ResourceLoader 对象
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}

if (resourceLoader instanceof ResourcePatternResolver) {
//比如之前的PathMatchingResourcePatternResolver
try {
// 获得 Resource 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
// 加载 BeanDefinition
int count = loadBeanDefinitions(resources);
// 添加到 actualResources 中
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
//只能通过绝对URL加载单个资源。
// 获得 Resource 对象
Resource resource = resourceLoader.getResource(location);
// 加载 BeanDefinition
int count = loadBeanDefinitions(resource);
// 添加到 actualResources 中
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
  • 首先,获取 ResourceLoader 对象。
  • 然后,根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource 。
  • 最终,都会回归到 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 方法,所以这是一个递归的过程。
  • 另外,获得到的 Resource 的对象或数组,都会添加到 actualResources 中。
处理相对路径

如果 location 是相对路径,则会根据相应的 Resource 计算出相应的相对路径的 Resource 对象 ,然后:

  • 若该 Resource 存在,则调用 XmlBeanDefinitionReader#loadBeanDefinitions() 方法,进行 BeanDefinition 加载。
  • 否则,构造一个绝对 location( 即 StringUtils.applyRelativePath(baseLocation, location) 处的代码),并调用 #loadBeanDefinitions(String location, Set actualResources) 方法,与绝对路径过程一样

小结

解析import的过程:获取 source 属性值,得到正确的资源路径,然后调用 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 方法,进行递归的 BeanDefinition 加载


七、解析import标签
http://www.muzili.ren/2022/06/11/解析import标签/
作者
jievhaha
发布于
2022年6月11日
许可协议