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

在上一文中我们分析了注册 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) {
// 添加到 actualResources
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(Resource... resources) 方法,进行递归的 BeanDefinition 加载。
本文由AnonyStar 发布,可转载但需声明原文出处。
仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码
《spring源码解读》 - IoC 之解析 import 标签的更多相关文章
- Spring源码:IOC原理解析(一)
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! IOC(Inversion of Control),即控制反转,意思是将对象的创建和依赖关系交给第三方容器处理,我们要用的时候告诉容器我们 ...
- Spring源码:IOC原理解析(二)
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 接着上一章节的内容,我们来分析当new一个FileSystemXmlApplicationContext对象的时候,spring到底做了那 ...
- Spring源码阅读-IoC容器解析
目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory Messa ...
- spring源码解读-ioc
本系列博客结合我的理解,对spring的ioc进行简单分析,欢迎大家批评指正. beanfactory 我们从beanfactory开始,beanfactory是最根部的容器,描述了整个ioc的一些规 ...
- spring源码浅析——IOC
=========================================== 原文链接: spring源码浅析--IOC 转载请注明出处! ======================= ...
- spring源码分析---IOC(1)
我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...
- Spring源码解读Spring IOC原理
一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...
- Spring 源码剖析IOC容器(一)概览
目录 一.容器概述 二.核心类源码解读 三.模拟容器获取Bean ======================= 一.容器概述 spring IOC控制反转,又称为DI依赖注入:大体是先初始化bean ...
- Spring源码解读之BeanFactoryPostProcessor的处理
前言 前段时间旁听了某课堂两节Spring源码解析课,刚好最近自己又在重新学习中,便在这里记录一下学习所得.我之前写过一篇博文,是介绍BeanFactoryPostProcessor跟BeanPost ...
随机推荐
- bzoj 1195 [HNOI2006]最短母串 bfs 状压 最短路 AC自动机
LINK:最短母串 求母串的问题.不适合SAM. 可以先简化问题 考虑给出的n个字符串不存在包含关系. 那么 那么存在的情况 只可能有 两个字符串拼接起来能表示另外一个字符串 或者某个字符串的后缀可以 ...
- bzoj 4238 电压
LINK:电压 一张图 每个点可以为黑点或百点 每一条边的两端都必须为一黑一白.询问又多少条边满足除了这条边不满足条件其余所有边都满足条件. 分析一下这个所谓的条件 每一条边的两端必须为一黑一白 所以 ...
- Java对象(创建过程、内存布局、访问方法)
(Java 普通对象.不包括数组.Class 对象等.) 对象创建过程 类加载 遇到 new 指令时,获取对应的符号引用,并检查该符号引用代表的类是否已被初始化.如果没有就进行类加载. 分配内存 ...
- Hadoop的序列化
普通的序列化需要将类型的继承结构也序列化,但是hadoop只序列化对象本身,忽略继承关系,因为hadoop中传输的自定义类型一般都是简单的类型,这样可以减少传输的序列化数据,降低网络带宽的使用.
- Spring学习总结(3)-了解Spring框架
Spring的核心Jar包 在Spring4的官方文档里,提到了Sping的核心包是:spring-context,只要引用了这个jar包,就可以实现Spring90%的基础功能.maven引用如下: ...
- 简单的 Nginx+Tomcat 配置负载均衡集群
简单 Nginx+Tomcat 配置负载均衡集群 前期准备 解压两个tomcat,修改端口号 server1:8081 server:8082 同时启动 nginx官网下载解压版nginx 创建一个简 ...
- 双下划线开头的attr方法
# class Foo: # x=1 # def __init__(self,y): # self.y=y # # def __getattr__(self, item): # print('执行__ ...
- Android线性布局和帧布局
第二次,本牛崽十分从容,今天咱们来讲讲Android Q之布局,我遇到的问题与自己学到的,大牛不要嘲笑哈,有错误可以指出来,本牛崽看到就改了. 今天我的学长跟我们开始了布局,布局看资料说好像有5种,又 ...
- javascript作用域、预解析笔记
1.作用域 一般情况下,一段代码中所用到的名字并不总是有效可用的, 而限定这个名字(变量)的可用性的代码范围就是这个名字的作用域,可用有效的减少变量名冲突 2.js的作用域(e ...
- HTML学习第三天
超链接: <a href=""></a> target链接打开方式 1._blank新窗口打开 2._self当前窗口打开 ...