原文出自:http://cmsblogs.com

在博客【死磕Spring】----- IOC 之 注册 BeanDefinition中分析到,Spring 中有两种解析 Bean 的方式。如果根节点或者子节点采用默认命名空间的话,则调用 parseDefaultElement() 进行默认标签解析,否则调用 delegate.parseCustomElement() 方法进行自定义解析。所以以下博客就这两个方法进行详细分析说明,先从默认标签解析过程开始,源码如下:

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 对 import 标签的解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 对 alias 标签的解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 对 bean 标签的解析
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 对 beans 标签的解析
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 import、alias、bean、beans。咱门从第一个标签 import 开始。

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 即可,这样大大简化了配置后期维护的复杂度,同时也易于管理。

Spring 利用 importBeanDefinitionResource() 方法完成对 import 标签的解析。

    protected void importBeanDefinitionResource(Element ele) {
// 获取 resource 的属性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 为空,直接退出
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
} // 解析系统属性,格式如 :"${user.dir}"
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) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
} // 绝对路径
if (absoluteLocation) {
try {
// 直接根据地质加载相应的配置文件
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()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
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[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

解析 import 过程较为清晰,整个过程如下:

  1. 获取 source 属性的值,该值表示资源的路径
  2. 解析路径中的系统属性,如"${user.dir}"
  3. 判断资源路径 location 是绝对路径还是相对路径
  4. 如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析
  5. 如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析
  6. 通知监听器,完成解析

判断路径

方法通过以下方法来判断 location 是为相对路径还是绝对路径:

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

判断绝对路径的规则如下:

  • 以 classpath*: 或者 classpath: 开头为绝对路径
  • 能够通过该 location 构建出 java.net.URL为绝对路径
  • 根据 location 构造 java.net.URI 判断调用 isAbsolute() 判断是否为绝对路径

绝对路径

如果 location 为绝对路径则调用 loadBeanDefinitions(),该方法在 AbstractBeanDefinitionReader 中定义。

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
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 = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}

整个逻辑比较简单,首先获取 ResourceLoader,然后根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource,但是最终都会回归到 XmlBeanDefinitionReader.loadBeanDefinitions() ,所以这是一个递归的过程。

相对路径

如果是相对路径则会根据相应的 Resource 计算出相应的绝对路径,然后根据该路径构造一个 Resource,若该 Resource 存在,则调用 XmlBeanDefinitionReader.loadBeanDefinitions() 进行 BeanDefinition 加载,否则构造一个绝对 location ,调用 AbstractBeanDefinitionReader.loadBeanDefinitions() 方法,与绝对路径过程一样。

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

【死磕 Spring】—– IOC 之解析Bean:解析 import 标签的更多相关文章

  1. 【死磕 Spring】----- IOC 之解析 bean 标签:开启解析进程

    原文出自:http://cmsblogs.com import 标签解析完毕了,再看 Spring 中最复杂也是最重要的标签 bean 标签的解析过程. 在方法 parseDefaultElement ...

  2. 死磕Spring之IoC篇 - BeanDefinition 的解析阶段(XML 文件)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  3. 死磕Spring之IoC篇 - 解析自定义标签(XML 文件)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  4. 死磕Spring之IoC篇 - BeanDefinition 的解析过程(面向注解)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  5. 【死磕 Spring】----- IOC 之 加载 Bean

    原文出自:http://cmsblogs.com 先看一段熟悉的代码: ClassPathResource resource = new ClassPathResource("bean.xm ...

  6. 死磕Spring之IoC篇 - 开启 Bean 的加载

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  7. 死磕Spring之IoC篇 - Bean 的创建过程

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  8. 死磕Spring之IoC篇 - @Bean 等注解的实现原理

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  9. Spring源码-IOC部分-Xml Bean解析注册过程【3】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

随机推荐

  1. Scrapy 和 scrapy-redis的区别

    Scrapy 和 scrapy-redis的区别 Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础 ...

  2. C语言实现计算双基回文数详解

    双基回文数的定义: 如果一个正整数n至少在两个不同的进位制(二进制<=进制=<十进制)b1和b2下都是回文数,则称n是双基回文数. 根据定义,简单的说就是在二进制到十进制之间(包括十进制和 ...

  3. 图解MySQL索引--B-Tree(B+Tree)

    看了很多关于索引的博客,讲的大同小异.但是始终没有让我明白关于索引的一些概念,如B-Tree索引,Hash索引,唯一索引....或许有很多人和我一样,没搞清楚概念就开始研究B-Tree,B+Tree等 ...

  4. nodejs中处理回调函数的异常

    如果是使用nodejs+express3这个经典的组合,那么有一种很方面的处理回调函数异常的方法: 1. 安装模块:express-domain-middleware 2. 加入如下的代码: app. ...

  5. Java多线程简介

    Java多线程简介 Java中内置了对多线程的支持,让多线程的开发方便很多,但同时也带来了另外的复杂,线程间的交互以及很多的不确定性让多线程又显得很复杂.在此只是针对Java中多线程的基础做些说明,有 ...

  6. BZOJ_2039_[2009国家集训队]employ人员雇佣_ 最小割

    BZOJ_2039_[2009国家集训队]employ人员雇佣_ 最小割 Description 作为一个富有经营头脑的富翁,小L决定从本国最优秀的经理中雇佣一些来经营自己的公司.这些经理相互之间合作 ...

  7. Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载)

    Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载) 说明:Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构,我采用以下三种维度来讲解 1.  代码层面. 2.  数 ...

  8. python 小练习题做起来

    1# 有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并# (按字母顺序排列), 输出到一个新文件C中.with open('a') as f1: a=f1.read()with op ...

  9. Javascript的内存泄漏分析

    作为程序员(更高大尚的称谓:研软件研发)的我们,无论是用Javascript,还是.net, java语言,肯定都遇到过内存泄漏的问题.只不过他们都有GC机制来帮助程序员完成内存回收的事情,如果你是C ...

  10. xamarin android如何将Java.Lang.Object类型转成C#类型

    问题起源 其实这个标题也可以换一个更准确一点,因为我遇到的问题是: xamarin android中的Class继承了Java.Lang.Object ,将json序列化成c#类型时发现无法赋值,序列 ...