Resource接口为应用提供了底层资源访问的能力。该接口的主要作用是描述实际的资源。
public interface Resource extends InputStreamSource {
//资源是否存在
boolean exists();
//资源是否可读
default boolean isReadable() {return true;}
//资源是否被打开
default boolean isOpen() {return false;}
//确定此资源是否代表文件系统中的文件
default boolean isFile() {return false;}
//返回资源的URL句柄
URL getURL() throws IOException;
//返回资源的URI句柄
URI getURI() throws IOException;
//返回资源的文件对象
File getFile() throws IOException;
//根据相对路径创建资源
Resource createRelative(String relativePath) throws IOException;
//资源名称
String getFilename();
//资源描述
String getDescription();
}
继承父类接口方法InputStream getInputStream() throw IOError:返回资源对应的输入流
ByteArrayResource:用于从任何给定的字节数组中加载内容,特别适用于从本地内容创建字节数组。因为isOpen()返回false。所有ByuteArrayResource可以多次使用。当然该资源也可以通过代码直接创建。
InputStreamResource:java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true
FileSystemResource:java.io.File资源,对于“getInputStream ”操作将返回底层文件的字节流,“isOpen”将永远返回false,从而表示可多次读取底层文件的字节流
ClassPathResource:类路径下的资源,资源以相对类路径的方式表示。该资源可以存放在如下的位置:1.类路径下的文件系统2.类路径下的jar中。
isOpen返回false,表示可以多次读取。ClassPathResource替代了Class类和ClassLoader类的getReesource(String name)和getResourceAsStream(String name)加载类路径资源的方式。提供一致的访问方式。构造函数如下:
public ClassPathResource(String path):使用默认的ClassLoader加载资源
ublic ClassPathResource(String path,ClassLoader classLoader):采用自定义的ClassLoader加载资源
ublic ClassPathResource(String path,Class> class):使用指定类加载器加载资源,加载的是相对于当前类的路径的资源
如果加载的是jar包中的资源可以采用getURL()获得资源路径,因为资源不在文件系统中所以无法使用getFile
WritableResource:可写资源接口,获取资源后,可以对该资源进行写操作。可以通过编程的方式动态的改变资源
FileSystemResource:文件系统资源
ServletContextResource:为web容器上下文中的资源服务。负责从相对于web根目录的路径下加载资源。支持以流和URL的方式访问。在war解压的情况下可以通过File的方式访问,该类也可以从jar中加载资源
UrlResource:是对java.net.URL的封装,可以访问任何以URL表示的资源。eg文件系统资源,FTP资源,HTTP资源。
PathResource:该类封装了java.net.URL,java.nio.file.path,文件系统资源,也就说凡是可以采用UrlResource和FileSystemResource表示的资源,也可以采用该类表示
前缀 | 示例 | 说明 | 备注 |
classpath: | classpath:aa/bb/cc/bean.xml | 类路径下的资源,资源可以在文件系统,也可以在jar/zip档案中 | classpath: 和classpath:/是等价的 |
file: | file:aa/bb/cc/bean.xml | 表示文件系统中资源,使用UrlResource表示 | |
http:// | http://http://mp.blog.csdn.net/bean.xml | 使用UrlResource从网络上的资源 | |
ftp:// | ftp://http://mp.blog.csdn.net/bean.xml | 使用UrlResource从FTP上的资源 | |
没有 | a/bb/cc/bean.xml | 根据ApplicationContext的具体实现类采用对应类型的Resource | 如果是web应用,采用ServletContextResource,否则采用calsspath:a/bb/cc/bean.xml |
通配符 | 示例 | 说明 | 备注 |
? | a/bb/c?/bean?.xml | 匹配单个字符 | |
* | a/bb/*/bean*.xml | 匹配一个或者多个字符 | |
** |
a/bb/**/bean**.xml | 匹配一个或者多个路径 | bean**.xml中**按照*处理 |
?和*是针对于字符匹配而**是针对于路径匹配
ResourceLoader接口用于实现不同的Resource加载策略,即将不同Resource实例的创建交给ResourceLoader,该接口只支持以classpath:不支持classpath*:如果想要支持后者的接口是ResourcePatternResolver
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:" */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* 按照给定的路径创建资源,支持如下几种模式
* 1.以file前缀描述的资源 eg:file:/work/bean.xml
* 2.以classpath前缀描述的资源 eg:classpath:/aa/bb/bean.xml
* 3.相对路径描述的资源 /WEB-INF/bean.xml
* 返回的资源并不一定存在需要调用Resource#exists判断
Resource getResource(String location);
/**
* 获得ResourceLoader的类加载器,在创建ClassPathResource时被传入
* 在某些需要使用加载器的应用中可以直接通过该方法获得而不是采用thread context ClassLoader
* 在某些情况下两个ClassLoader并不一样
*/
@Nullable
ClassLoader getClassLoader();
}
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* 根据给定路径,获取该路径下的所有的Resource
*/
Resource[] getResources(String locationPattern) throws IOException;
}
是ResourceLoader提供的默认实现,具体流程如下
1.判断liocation是否定义了协议:file,zip,jar,war...如果定义了直接返回该资源
2.是否以/开始,如果是就在类路径的上下文中加载该资源
3.判断是否以classpath:开始,如果是创建ClassPathResource
4.尝试创建URLResource
5.创建UrlResource失败后,就在类路径的上下文中加载该资源
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
//资源是否定义了协议eg:file,zip,http
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//判断是否为相对路径
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//是否以classpath:
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 创建URLResource
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
//在文件系统中加载资源,getResourceByPath会在FileSystemResourceLoader中重写
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
加载文件系统中的资源
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemContextResource(path);
}
从Servlet上下文路径中加载资源它实现了自己的getResourceByPath方法,这里的path即使以”/”开头,也是相对ServletContext的路径,而不是绝对路径,要使用绝对路径,需要添加”file:”前缀
protected Resource getResourceByPath(String path) {
return new ServletContextResource(this.servletContext, path);
}
匹配模式下的资源加载,持有ResourceLoader接口,不含有地址通配符的情况下要么采用默认的方式,要么采用具体的实现方式加载对应的资源
private final ResourceLoader resourceLoader;
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
含有地址通配符查找主要分如下几种情况
case 1:以classpath*开始且路径中含有通配符
case 2:以classpath*开始且路径中不含有通配符
case 3:不以classpath*开始且路径中含有通配符
case 4:不以classpath*开始且路径中不含有通配符
其中case 1和case 3执行的逻辑一致,case 4直接采用ClassLoader加载资源比较简单
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null”);
//是否以classpath*:开始
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
// case 1:以classpath*开始且路径中含有通配符
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
//case 2: 以classpath*开始且路径中不含通配符
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
//case 3: 不以classpath*开始且路径中含有通配符和case 1处理的逻辑一致
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
//case 4: 不以classpath*开始且路径中不含通配符
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
case 1:以classpath*开始且路径中含有通配符
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//确定根路径,该路径中不含有任何的通配符的最长路径
String rootDirPath = determineRootDir(locationPattern);
//rootDirPath之后的包含模式匹配字符的路径信pattern
String subPattern = locationPattern.substring(rootDirPath.length());
//递归查找该根路径下匹配的资源
Resource[] rootDirResources = getResources(rootDirPath);
Set result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
if (equinoxResolveMethod != null) {
if (rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
//创建UrlResource
rootDirResource = new UrlResource(rootDirUrl);
}
}
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
//jar中资源
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
//非jar中的资源
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
从代码分析case 1分为3种case
1.查找并创建VfsResource
2.在jar中查找Resource,步骤如下
1.计算当前Resource在Jar文件中的根路径
2.判断路径
3.创建资源并添加到结果集中
protected Set doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
……
//计算当前Resource在Jar文件中的根路径
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set result = new LinkedHashSet<>(8);
for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
//以rootEntryPath开头
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
//相对路径和subPattern匹配
if (getPathMatcher().match(subPattern, relativePath)) {
//创建一个Resource。将新创建的Resource添加入结果集中
result.add(rootDirResource.createRelative(relativePath));
}
}
}
return result;
}
}
2.在非jar中查找Resource,步骤如下
1.获取资源根路径的绝对路径
2.获得该路径下的所有文件
3.路径匹配
protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
File rootDir;
try {
//获得资源绝根路径的对路径
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
return doFindMatchingFileSystemResources(rootDir, subPattern);
}
protected Set doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
}
Set matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set result = new LinkedHashSet<>(matchingFiles.size());
for (File file : matchingFiles) {
//创建资源并添加到结果集
result.add(new FileSystemResource(file));
}
return result;
}
protected Set retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
….
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
fullPattern += "/";
}
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set result = new LinkedHashSet<>(8);
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {
File[] dirContents = dir.listFiles();
Arrays.sort(dirContents);
for (File content : dirContents) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
//路径匹配
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
//递归调用
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
case 2: 以classpath*开始且路径中不含通配符,步骤如下
1.采用ClassLoader#getResources(String) 获取资源URL
2.遍历URL创建资源
3.如果截取后的path=“”,根据ClassLoaer的类型创建资源,
4.如果ClassLoader类型不匹配,就采用父加载器查找并创建资源
protected Resource[] findAllClassPathResources(String location) throws IOException {
……
Set result = doFindAllClassPathResources(path);
……
return result.toArray(new Resource[result.size()]);
}
protected Set doFindAllClassPathResources(String path) throws IOException {
ClassLoader cl = getClassLoader();
//采用ClassLoaer获取资源
Enumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
//创建Resource
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
// 从jar中加载资源
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
protected Resource convertClassLoaderURL(URL url) {
return new UrlResource(url);
}
protected void addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set result) {
if (classLoader instanceof URLClassLoader) {
try {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
try {
//创建Resource
UrlResource jarResource = new UrlResource(
ResourceUtils.JAR_URL_PREFIX + url.toString() + ResourceUtils.JAR_URL_SEPARATOR);
if (jarResource.exists()) {
result.add(jarResource);
}
}
……
}
}
……
}
if (classLoader == ClassLoader.getSystemClassLoader()) {
// "java.class.path" manifest evaluation...
//从java.class.path加载资源
addClassPathManifestEntries(result);
}
if (classLoader != null) {
try {
// Hierarchy traversal...
//从父加载器中查找资源
addAllClassLoaderJarRoots(classLoader.getParent(), result);
}
……
}
}
protected void addClassPathManifestEntries(Set result) {
try {
String javaClassPathProperty = System.getProperty("java.class.path");
for (String path : StringUtils.delimitedListToStringArray(
javaClassPathProperty, System.getProperty("path.separator"))) {
try {
File file = new File(path);
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
ResourceUtils.FILE_URL_PREFIX + file.getAbsolutePath() +
ResourceUtils.JAR_URL_SEPARATOR);
if (jarResource.exists()) {
result.add(jarResource);
}
}
……
}
}
……
}
case 3: 不以classpath*开始且路径中含有通配符
return findPathMatchingResources(locationPattern);
case 4: 不以classpath*开始且路径中不含通配符
直接采用ClassLoader加载资源
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
对classpath下的资源,相同名字的资源可能存在多个,如果使用”classpath*:”作为前缀,表明需要找到classpath下所有该名字资源,因而需要调用findClassPathResources方法查找classpath下所有该名称的Resource,对非classpath下的资源,对于不存在模式字符的location,一般认为一个location对应一个资源,因而直接调用ResourceLoader.getResource()方法即可(对classpath下没有以”classpath*:”开头的location也适用)
重写了父类的查找逻辑,它使用ServletContext.getResourcePaths()方式来查找参数目录下的文件,而不是File.listFiles()方法
protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
if (rootDirResource instanceof ServletContextResource) {
ServletContextResource scResource = (ServletContextResource) rootDirResource;
ServletContext sc = scResource.getServletContext();
String fullPattern = scResource.getPath() + subPattern;
Set result = new LinkedHashSet<>(8);
doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);
return result;
}
else {
return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
}
}
protected void doRetrieveMatchingServletContextResources(
ServletContext servletContext, String fullPattern, String dir, Set result)
throws IOException {
Set candidates = servletContext.getResourcePaths(dir);
}
war/jar/zip中的资源如果使用resource.getFile()会抛出FileNotFoundError.此时需要使用Resource.getInputStream()读取资源。
1. Spring源码
2.https://www.cnblogs.com/doit8791/p/5774743.html
3.https://yq.aliyun.com/articles/46894