导航
导航
文章目录
  1. 一、前言
  2. 二、分析
    1. 2.1 DefaultResourceLoader
    2. 2.2 ResourceLoader
    3. 2.3 DefaultResourceLoader 具体实现
    4. 2.4 FileUrlResource
  3. 三、自定义文件协议解析器
  4. 四、小结

[断点分析之spring-ioc]-资源加载ResourceLoader(二)

一、前言

​ 资源统一抽象为Resource对象.可曾记得在 spring 配置文件中的这种写法:classpath:com/sjr/test/bean/MyTestBean.xml,那么这种写法的意思是从classpath路径下加载xml,那么spring是如何定位到文件的?

​ 上面这种写法相当于是个协议,在spring中默认支持9种文件协议.

  • URL_PROTOCOL_FILE

    从文件系统中加载文件

  • URL_PROTOCOL_JAR

    从jar包中加载文件

  • URL_PROTOCOL_WAR

    从war包中加载文件

  • URL_PROTOCOL_ZIP

    从zip中加载文件

  • URL_PROTOCOL_WSJAR

    从wsjar中加载文件

  • URL_PROTOCOL_VFSZIP

    从vfszip中加载文件

  • URL_PROTOCOL_VFSFILE

    从vfsfile中加载文件

  • URL_PROTOCOL_VFS

    从vfs中加载文件

二、分析

​ 这个故事要从一段代码开始

1
2
3
4
5
6
7
8
@Test
public void testSpringResourceLoader(){
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(this.getClass().getClassLoader());
BeanFactory factory = new XmlBeanFactory(defaultResourceLoader.getResource("classpath:com/sjr/test/bean/MyTestBean.xml"));
final MyTestBean testBean = factory.getBean("myTestBean",MyTestBean.class);
final String testStr = testBean.getTestStr();
System.out.println(testStr);
}

​ 之前的代码,我们是直接使用的ClassPathResource来加载文件,这里使用的DefaultResourceLoader对象来加载文件.那DefaultResourceLoader有什么用处呢?

  1. 自动检测文件该如何加载

    1. 简化文件加载操作流程

2.1 DefaultResourceLoader

DefaultResourceLoaderResourceLoader的默认实现.

images

上图可以看到 DefaultResourceLoader 还有3个子类进行了功能的扩展.先看看ResourceLoader这个接口.

2.2 ResourceLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public interface ResourceLoader {

/**
* classpath: 前缀常量
* */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;


/**
* 通过 路径 获取 Resource 对象
*/
Resource getResource(String location);

/**
* 获取类加载器
*/
@Nullable
ClassLoader getClassLoader();

}

​ 从代码可以看出,该接口只有2个方法,一个是通过路径获取 Resource 对象,另外一个是获取类加载器.那么在来看看 DefaultResourceLoader 的默认实现代码吧.

2.3 DefaultResourceLoader 具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

public class DefaultResourceLoader implements ResourceLoader {

@Nullable
private ClassLoader classLoader;
// 协议解析器 set
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
// 缓存
private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);


/**
* 使用默认构造器,默认构造器中使用默认的类加载器
*/
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}

/**
* 使用指定的类加载器
*/
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}


/**
* 设置类加载器
*/
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}

/**
* 获取类加载器
*/
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}

/**
* 添加协议解析器
*/
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}

/**
* 获取协议解析器集合
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;
}

/**
* 获取资源缓存
*/
@SuppressWarnings("unchecked")
public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
}

/**
* 清除所有资源缓存
*/
public void clearResourceCaches() {
this.resourceCaches.clear();
}

/**
* 获取资源
* **/
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 遍历所有协议解析器
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
// 解析资源
Resource resource = protocolResolver.resolve(location, this);
// 如果资源解析到则返回 resource 对象
if (resource != null) {
return resource;
}
}
// 判断是否是/开头
if (location.startsWith("/")) {
// 获取classpath上下文中的资源
return getResourceByPath(location);
}
// 判断是否是classpath:开头路径,如果是则从classpath中获取资源
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 尝试把路径转化为url
// Try to parse the location as a URL...
URL url = new URL(location);
// 判断是文件资源 还是url资源
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 非url 尝试从 classpath 上下文中获取资源
return getResourceByPath(location);
}
}
}

/**
* 通过路径获取 Resource 对象
* 从 classPath 中加载文件
*/
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}


protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {

public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}

@Override
public String getPathWithinContext() {
return getPath();
}

/**
* 创建相对路径的 Resource 对象
*/
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}

}

​ 以上代码的逻辑比较简单明了,核心逻辑在 getResource 这个方法中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 遍历所有协议解析器
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
// 解析资源
Resource resource = protocolResolver.resolve(location, this);
// 如果资源解析到则返回 resource 对象
if (resource != null) {
return resource;
}
}
// 判断是否是/开头
if (location.startsWith("/")) {
// 获取classpath上下文中的资源
return getResourceByPath(location);
}
// 判断是否是classpath:开头路径,如果是则从classpath中获取资源
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 尝试把路径转化为url
// Try to parse the location as a URL...
URL url = new URL(location);
// 判断是文件资源 还是url资源
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 非url 尝试从 classpath 上下文中获取资源
return getResourceByPath(location);
}
}
}

​ 逻辑流程为以下几步:

  1. 判断是否设置了 协议解析器,如果设置了,则遍历所有的协议解析,

    并解析文件,如果解析成功则返回 Resource 对象否则执行第二步.

    1. 判断路径是否是 / 开头,若是则从classPath 加载文件,调用

    getResourceByPath方法,返回 Resource 对象

    1. 判断是否是 classpath: 开头,若是则从 classPath 加载文件

    2. 若是以上几步都失败,则尝试把路径转为URL,如果成功则返回

    FileUrlResourceUrlResource 对象

    1. 最后挣扎以下,从classPath 加载文件

2.4 FileUrlResource

​ 开篇说道 spring 默认支持 9中协议(如果把 classPath 也算上的话),那么除了 常用的 classPath 以外,其他的怎么使用呢?其他的笔者本人都没用过多少,就来看看 file 协议吧.

1
2
3
4
5
6
7
8
9

@Test
public void testSpringResourceLoaderForFileProtocol(){
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(this.getClass().getClassLoader());
BeanFactory factory = new XmlBeanFactory(defaultResourceLoader.getResource("file:///src/test/resources/com/sjr/test/bean/MyTestBean.xml"));
final MyTestBean testBean = factory.getBean("myTestBean",MyTestBean.class);
final String testStr = testBean.getTestStr();
System.out.println(testStr);
}

​ 相当于是个绝对路径了.其他协议可以查查资料.

三、自定义文件协议解析器

​ 在DefaultResourceLoader 中的核心代码中有段遍历解析器的代码,来瞧瞧.

1
2
3
4
5
6
7
8
9
// 遍历所有协议解析器
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
// 解析资源
Resource resource = protocolResolver.resolve(location, this);
// 如果资源解析到则返回 resource 对象
if (resource != null) {
return resource;
}
}

​ 通过这段代码,可以实现自定义文件协议解析器的逻辑,方便扩展.ProtocolResolver是个接口,里面就一个方法,非常简单.

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface ProtocolResolver {

/**
* 解析
*/
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);

}

​ 该接口也是个函数接口(可以使用Lambda表达式).来实现一个协议试一试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 实现ProtocolResolver 接口 自定义解析逻辑
public class SjrProtocolResolver implements ProtocolResolver {

@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if(resourceLoader == null){
return null;
}
if(location == null || !location.startsWith("sjr")){
return null;
}
final int index = location.indexOf("sjr:");
return resourceLoader.getResource(location.substring(index + 4));
}
}
1
2
3
4
5
6
7
8
9
@Test
public void testSpringProtocolResolverOfAdv(){
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader(this.getClass().getClassLoader());
defaultResourceLoader.addProtocolResolver(new SjrProtocolResolver());
BeanFactory factory = new XmlBeanFactory(defaultResourceLoader.getResource("sjr:com/sjr/test/bean/MyTestBean.xml"));
final MyTestBean testBean = factory.getBean("myTestBean",MyTestBean.class);
final String testStr = testBean.getTestStr();
System.out.println(testStr);
}

​ 这样就完成了自定义协议的解析.

DefaultResourceLoader 还有三个子类:

  • ServletContextResourceLoader

    返回ServletContextResource,从ServletContext获取资源.

  • FileSystemResourceLoader

    返回FileSystemContextResource ,从文件系统中获取资源,

    其本质上是FileSystemResource,实现了ContextResource接口

  • ClassRelativeResourceLoader

    返回ClassRelativeContextResource ,从classPath获取资源,

    其本质上是ClassPathResource,实现了ContextResource接口

    这三个子类,都是做的简单扩展,逻辑简单,有兴趣可以去看看.

四、小结

​ 文件是加载到,那么spring 是怎么解析xml文件的呢?

支持一下
扫一扫,请我吃颗大白兔奶糖
  • 支付宝扫一扫