您的位置:首页 > 编程语言 > Java开发

《spring源码解读》 - IoC 之解析 import 标签

2020-09-02 15:43 1431 查看

在上一文中我们分析了注册

BeanDefinition
的过程,在其中我们了解到在解析跟节点和子节点时分两种情况,对于默认名称空间的标签我们通过
DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
进行处理,而对于自定义标签则通过
BeanDefinitionParserDelegate#parseCustomElement(Element ele)
方法进行处理。


这里我们首先对默认名称空间的解析进行开始解读,
#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法的代码如下:

public static final String NESTED_BEANS_ELEMENT = "beans";

public static final String ALIAS_ELEMENT = "alias";

public static final String NAME_ATTRIBUTE = "name";

public static final String ALIAS_ATTRIBUTE = "alias";

public static final String IMPORT_ELEMENT = "import";
/**
* 如果根节点或者子节点采用默认命名空间的话 采用默认的解析方式
* @param ele
* @param delegate
*/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// import 标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
//alias 标签
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
//处理 bean 标签 这是spring中很核心的标签处理
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 处理 beans 标签
doRegisterBeanDefinitions(ele);
}
}
  • 通过上述代码我们可以的看到默认标签包含
    import
    alias
    bean
    beans
    , 本文将对
    import
    的解析进行解读

1. Import 案例

经历过

Spring
配置文件的小伙伴都知道,如果工程比较大,配置文件的维护会让人觉得恐怖,文件太多了,想象将所有的配置都放在一个
spring.xml
配置文件中,哪种后怕感是不是很明显?
所有针对这种情况
Spring
提供了一个分模块的思路,利用
import
标签,例如我们可以构造一个这样的
spring.xml

<?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">

<import resource="spring-student.xml"/>

<import resource="spring-student-dtd.xml"/>

</beans>

spring.xml
配置文件中,使用
import
标签的方式导入其他模块的配置文件。

  • 如果有配置需要修改直接修改相应配置文件即可。
  • 若有新的模块需要引入直接增加
    import
    即可。

这样大大简化了配置后期维护的复杂度,同时也易于管理。

2. importBeanDefinitionResource

DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
方法用户解析
import
标签,方法代码如下:

protected void importBeanDefinitionResource(Element ele) {
// 1.获取 节点 属性resource的值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
//2. 判断是否为空,为空直接返回
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}

//3.解析 系统属性  ${user.dir}
// Resolve system properties: e.g. "${user.dir}"
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

// 实际 Resource 集合, 即 import 的地址,
Set<Resource> actualResources = new LinkedHashSet<>(4);

// Discover whether the location is an absolute or relative URI
// 检查路径 location 是绝对路径还是相对路径
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*:"
}

// Absolute or relative?
// 绝对路径
if (absoluteLocation) {
try {
// 解析 location 得到 resource 并且添加到 actualResources中
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;
// 解析 location 路径,得到相对路径的 Resource relativeResource
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//存在
if (relativeResource.exists()) {
// 加载 resource 中的 Definition
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
// 添加 Resource 到 relativeResource
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);
}
}
// 解析成功后,进行监听器激活处理
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

解析 import 标签的过程较为清晰,整个过程如下

  • <1> 处,获取

    Resource
    属性的值,该值表示资源的路径。

  • <2> 处,解析路径中的系统属性,如

    "${user.dir}"

  • <3> 处,判断资源路径

    location
    是绝对路径还是相对路径。详细解析,见 「2.1 判断路径」 。

  • <4> 处,如果是绝对路径,则调递归调用

    Bean
    的解析过程,进行另一次的解析。详细解析,见 「2.2 处理绝对路径」 。

  • <5> 处,如果是相对路径,则先计算出绝对路径得到

    Resource
    ,然后进行解析。详细解析,见 「2.3 处理相对路径」 。

  • <6> 处,通知监听器,完成解析。

  • 上述代码的执行过程 UML 如下:


2.1 判断路径

在上述代码中,通过判断

location
是否是绝对路径的代码如下:

absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
  • ResourcePatternUtils.isUrl(location)
    如果是以
    classpath*:
    或者
    classpath:
    开头则为绝对路径,能够通过该
    location
    构建
    java.net.URL
    为绝对路径
  • 根据
    location
    构建
    java.net.URI
    判断调用
    #isAbsolute()
    方法,判断是否为绝对路径

2.2 处理绝对路径


如果 

location
 为绝对路径,则调用 
#loadBeanDefinitions(String location, Set<Resource> actualResources)
, 方法。该方在 
org.springframework.beans.factory.support.AbstractBeanDefinitionReader
 中定义,代码如下:

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) {
// Resource pattern matching available.
try {
// 获得 Resource 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。例如说,Ant 风格的 location
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 加载 BeanDefinition 们
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
// 添加到 actua
ad8
lResources
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.
// 获得 Resource 对象,
Resource resource = resourceLoader.getResource(location);
// 加载 BeanDefinition 们
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
// 添加到 actualResources
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
  • 上述代码执行过程的
    UML
    图如下





整个逻辑比较简单 :

  • 首先,获取 ResourceLoader 对象。
  • 然后,根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource 。
  • 最终,都会回归到
    XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources)
    方法,所以这是一个递归的过程。
  • 另外,获得到的 Resource 的对象或数组,都会添加到
    actualResources
    中。

2.3 处理相对路径


如果

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

  • 若该

    Resource
    存在,则调用
    XmlBeanDefinitionReader#loadBeanDefinitions()
    方法,进行
    BeanDefinition
    加载。

  • 否则,构造一个绝对

    location
    ( 即
    StringUtils.applyRelativePath(baseLocation, location)
    处的代码),并调用
    #loadBeanDefinitions(String location, Set<Resource> actualResources)
    方法,与绝对路径过程一样

3. 小结


至此,

import
标签解析完毕,整个过程比较清晰明了:获取
source
属性值,得到正确的资源路径,然后调用
XmlBeanDefinitionReader#loadBeanDefinitions(Re
1086
source... resources)
方法,进行递归的
BeanDefinition
加载。

本文由AnonyStar 发布,可转载但需声明原文出处。
仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: