共计 7316 个字符,预计需要花费 19 分钟才能阅读完成。
在上一文中咱们剖析了注册 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 公布, 可转载但需申明原文出处。
企慕「优雅编码的艺术」深信游刃有余,致力扭转人生
欢送关注微信公账号:云栖简码 获取更多优质文章
更多文章关注笔者博客:云栖简码