1、资源表示

spring中通过org.springframework.core.io.Resource作为所有资源的抽象与访问接口。

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
public interface Resource extends InputStreamSource {
//资源是否存在
boolean exists();
//资源是否可读
default boolean isReadable() {return exists();}
//资源是否被打开
default boolean isOpen() {return false;}
//是否为文件
default boolean isFile() {return false;}
//返回资源的URL句柄
URL getURL() throws IOException;
//返回资源的URI句柄
URI getURI() throws IOException;
//返回资源的File句柄
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
//资源内容长度
long contentLength() throws IOException;
//最后修改时间
long lastModified() throws IOException;
//根据资源的相对路径创建资源
Resource createRelative(String relativePath) throws IOException;
//获取资源文件名
@Nullable
String getFilename();
//获取资源的描述
String getDescription();
}

部分实现Resource的类的结构图:
Resource.png

  • AbstractResource,实现了Resource接口中大量的公共方法。
  • FileSystemResource,以文件或者URL的形式对该资源进行访问。
  • InputStreamResource,将给定的 InputStream 作为一种资源的 Resource 的实现类。
  • ByteArrayResource,对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
  • AbstractFileResolvingResource, 继承 AbstractResource,重写了一些公共方法。
  • UrlResource,对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
  • ClassPathResource,class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。

2、资源加载

ResourceLoader 接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。
ResourceLoader.png

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
public interface ResourceLoader {
/** 从类路径加载: "classpath:". */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* 返回指定资源位置的资源句柄。
* 句柄应该始终是一个可重用的资源描述符,允许多个 {@link Resource#getInputStream()} 调用。
* <li>必须支持完全限定的url, e.g. "file:C:/test.dat".
* <li>必须支持类路径伪url, e.g. "classpath:test.dat".
* <li>应该支持相对的文件路径吗, e.g. "WEB-INF/test.dat".
* (这将是特定于实现的,通常由ApplicationContext实现提供。)
* </ul>
* <p>注意,资源句柄并不意味着现有资源;
* 您需要调用{@link Resource#exists}来检查是否存在.
* @param location the resource location
* @return a corresponding Resource handle (never {@code null})
* @see #CLASSPATH_URL_PREFIX
* @see Resource#exists()
* @see Resource#getInputStream()
*/
Resource getResource(String location);
/**
* 获取这个ResourceLoader使用的ClassLoader.
* 需要直接访问类加载器的客户机可以使用ResourceLoader以统一的方式访问类加载器,而不是依赖于线程上下文类加载器。
* @return the ClassLoader
* (only {@code null} if even the system ClassLoader isn't accessible)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}

DefaultResourceLoader是ResourceLoader接口的默认实现,其Resource#getResource(String location)方法返回一个资源。

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
   @Override
public Resource getResource(String location) {
//非空检验
Assert.notNull(location, "Location must not be null");
//资源解析策略
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
//解析资源
Resource resource = protocolResolver.resolve(location, this);
//解析成功就返回
if (resource != null) {
return resource;
}
}

//如果location以“/”开头,则构造ClassPathContextResource。
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//如果location以"classpath:"开头,则构造ClassPathResource
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
//要不然就构造FileUrlResource或者UrlResource
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
//若在加载过程中抛出 MalformedURLException 异常,
//则委派 getResourceByPath() 实现资源定位加载。
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}

ProtocolResolver ,用户自定义协议资源解决策略,作为 DefaultResourceLoader 的 SPI,它允许用户自定义资源加载协议,而不需要继承 ResourceLoader 的子类。
ProtocolResolver 接口,仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader),该方法接收两个参数:资源路径location,指定的加载器 ResourceLoader,返回为相应的 Resource 。
调用 DefaultResourceLoader.addProtocolResolver()将自定义的资源解析策略加入spring中。

以上只能根据location返回一个Resource,ResourcePatternResolver提供了一个可以新的协议开头“classpath*:”,和方法Resource[] getResources(String locationPattern) throws IOException;
返回多个Resource。其子类PathMatchingResourcePatternResolver实现了这个方法,让然也实现了获取一个Resource的方法。

getResources.png

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
@Override
public Resource[] getResources(String locationPattern) throws IOException {
//非空校验
Assert.notNull(locationPattern, "Location pattern must not be null");
//如果locationPattern以"classpath*:"开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
//路径包含通配符,AntPathMatcher
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
//则通过Resource[] findPathMatchingResources(String locationPattern)方式解析
return findPathMatchingResources(locationPattern);
}
else {
//不包含通配符则 Resource[] findAllClassPathResources(String location) 方法解析
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
//有通配符
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
//单个资源
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
  • findPathMatchingResources
    当 locationPattern 以 classpath*: 开头且当中包含了通配符,则调用该方法进行资源加载。
  • findAllClassPathResources
    当 locationPattern 以 classpath*: 开头但是不包含通配符,则调用findAllClassPathResources() 方法加载资源。该方法返回 classes 路径下和所有 jar 包中的所有相匹配的资源。

3、总结

  • Spring 提供了 Resource 和 ResourceLoader 来统一抽象整个资源及其加载。使得资源与资源的加载有了一个更加清晰的界限。
  • AbstractResource 为 Resource 的默认实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可。
  • DefaultResourceLoader 为 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。
  • DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 Resource getResource(String location) 也实现了 Resource[] getResources(String locationPattern)。

tencent.jpg