Spring系列-1.2 Resource解析
Spring版本:Spring 5.2.9.BUILD-SNAPSHOT
修改过部分源码,但不影响主体流程
Resource简介#
Spring对各种底层资源,比如文件系统中的一个文件,classpath
上的一个文件,或者一个网络URL,统一抽象为接口`Resource``来表示
源加载策略需要满足如下要求:
-
职能划分清楚。资源的定义和资源的加载应该要有一个清晰的界限。
-
统一的抽象,统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。
实现类#
Resource
接口继承了InputStreamSource
接口通过getInputStream()
方法获取文件输入流。
下面是Resource接口继承图
实现类 | 介绍 |
---|---|
ClassPathResource |
Java类路径上的一个资源文件,比如一个class文件,xml/properties 配置文件 |
FileSystemResource |
操作系统文件系统中的一个资源文件 |
UrlResource |
将一个网络URL 地址封装成一个资源文件 |
ByteArrayResource |
将一个字节数组封装成一个Resource |
ServletContextResource |
获取ServletContext 环境下的资源文件,在web应用程序的根目录下解释相对路径 |
InputStreamResource |
将一个输入流封装成一个资源,因为用于描述一个已经打开的资源 |
VfsResource |
针对JBoss VFS 的资源 |
ResourceLoader#
-
Resource
定义了统一的资源,那资源的加载则由ResourceLoader
来统一定义。 -
通过
ResourceLoader
,给定其可以接受的资源路径,我们可以获得对应资源的Resource
对象,然后进行进行相应的资源访问。
接口定义如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
getResource()根据所提供资源的路径 location
返回 Resource
实例,但是它不确保该 Resource
一定存在,需要调用 Resource.exist()
方法判断。该方法支持以下模式的资源加载:
-
URL位置资源,如"file:C:/test.dat"
-
ClassPath位置资源,如"classpath:test.dat"
-
相对路径资源,如"WEB-INF/test.dat",此时返回的Resource实例根据实现不同而不同
该方法的主要实现是在其子类 DefaultResourceLoader
中实现。
getClassLoader()
返回 ClassLoader
实例,对于想要获取 ResourceLoader
使用的 ClassLoader
用户来说,可以直接调用该方法来获取。
实现类#
DefaultResourceLoader#
核心实现DefaultResourceLoader#getResource
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
// 处理带有classpath标识的resource
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
// 处理URL标识的resource定位
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 如果既不是classpath,也不是url标识的resource定位,则把getResource的重任交给getResourceByPath,
// 此方法是protected方法,默认的实现是得到一个ClassPathContextResource,此方法常常会用子类来实现
return getResourceByPath(location);
}
}
}
流程#
DefaultResourceLoader
会以以下顺序识别路径 :
-
轮询每个
ProtocolResolver
看它们哪个可以处理该路径,如果可以让其处理并返回相应的资源; -
如果路径以"/"开头,尝试将其处理为一个
ClassPathContextResource
并返回; -
如果路径以"classpath:"前缀开头,去除路径中前缀部分之后将其封装成一个
ClassPathResource
并返回; -
如果路径是
URL
格式,如果路径以"file:"/“vfs:”/"vfsfile:"前缀开头,将其封装成一个FileUrlResource
并返回;否则将其封装成一个UrlResource
返回;
5.其他格式,仍然尝试将其封装一个ClassPathContextResource
并返回;
ResourcePatternResolver#
使用DefaultResourceLoader
每次只能根据location
返回一个Resource
实例,当需要加载多个资源时就需要多次调用,ResourcePatternResolver
便是ResourceLoader
的扩展,它支持根据指定的资源路径匹配模式每次返回多个Resource
实例。
核心定义#
public interface ResourcePatternResolver extends ResourceLoader {
/**
* 在所有根目录下搜索文件的伪URL的前缀
* 与ResourceLoader中classpath不同的地方在于,此前缀会在所有的JAR包的根目录下搜索指定文件。
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* 返回指定路径下所有的资源对象。
* 返回的对象集合应该有Set的语义,也就是说,对于同一个资源,只应该返回一个资源对象
* @param locationPattern the location pattern to resolve
* @return the corresponding Resource objects
* @throws IOException in case of I/O errors
*/
Resource[] getResources(String locationPattern) throws IOException;
}
PathMatchingResourcePatternResolver#
核心实现PathMatchingResourcePatternResolver#getResources
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// 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)};
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!