《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 发布,可转载但需声明原文出处。
仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码
相关文章推荐
- 【死磕 Spring】—– IOC 之解析Bean:解析 import 标签
- spring源码深度解析— IOC 之 默认标签解析(下)
- spring 3源码解析之如何解析"import", "alias", "bean"标签
- spring 之 import标签、alias标签、beans标签 解析
- Spring源码解析之一 ------ 默认标签的解析注册(IOC的第一步)
- spring源码附录(8)import、beans标签的解析
- spring源码深度解析— IOC 之 默认标签解析(下)
- bean标签的解析及注册(四)对alias,import,beans标签的处理
- spring源码深度解析— IOC 之 自定义标签解析
- 使用import简化spring的配置 spring import 标签的解析 使用import或加载spring配置时,报错误There is no ID/IDREF 多个Spring配置文件import resource路径配置
- Spring IoC 自定义标签解析
- 4.2 spring-import 标签的解析;
- spring源码深度解析— IOC 之 默认标签解析(上)
- 【死磕 Spring】----- IOC 之解析 bean 标签:开启解析进程
- bean标签的解析及注册。
- Jsoup学习笔记2:Jsoup解析HTML代码标签与属性
- spring配置文件中非bean标签的原理解析
- 让IoC动态解析自定义配置(提供基于Unity的实现)
- android IOC框架解析(上)
- spring源码(7)alias标签的解析