Java中处理资源的核心接口是InputStream和OutputStream,但是用起来不是特别方便,spring在此基础上做了进一步的封装,形成了以Resource接口为核心的一套资源体系。
首先看下Resource接口:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
对于不同类型的资源,其实现是不同的,比如处理classpath下的资源就和处理文件系统下的资源方式不同。但是其行为都可以统一抽象为上面的接口。
下面分别看几个调用的例子:
Properties properties1 = new Properties();
Resource r1 = new ClassPathResource("a.properties");
properties1.load(r1.getInputStream());
System.out.println(properties1.getProperty("name"));
Resource r2 = new FileSystemResource("/Users/miracle/Desktop/1.properties");
Properties properties2 = new Properties();
properties2.load(r2.getInputStream());
System.out.println(properties2.getProperty("name"));
Resource r3 = new UrlResource("http://localhost:8080/test");
System.out.println(IOUtils.toString(r3.getInputStream(), "utf-8"));
分别是基于classpath、文件系统以及网络url的三种实现。对于读取而言,都是先构造一个Resource实例,然后拿到其内部的InputStream,然后读取。
其实,基于上面的方式,已经可以比较容易的获取资源了,但是spring提供了更加强大的功能,ResourceLoader接口:
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:" */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
通过一个ResourceLoader接口实例,我们可以无需关心Resource具体的实现类型,只要按照ResourceLoader的约定传入不同的资源路径,ResourceLoader会根据资源路径的格式做出相应的判断,查找对应类型的资源。
该接口的默认实现是DefaultResourceLoader,可以看下其getResource方法的实现:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
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 url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
首先判定传入的路径参数是否以“classpath:”开头,如果是的话,就按照classpathresource的方式来加载资源,否则的话按照urlresource的方式加载(注意!!!该模式既可以处理files:xxx格式的文件资源,也可以处理url的),如果还不行,仍然以classpath的方式解析。
如上方式是不支持ant风格的路径的,也就是一次只能匹配一个资源。所以,spring中常用的是其子接口:ResourcePatternResolver
该接口增加了Ant风格的资源路径,可以返回多个。实现类为:PathMatchingResourcePatternResolver。
classpath*:xxx
classpath:xxx
前者可以匹配classpath下所有符合条件的资源,后者只能匹配一个。
另外,也可以在路径中加入通配符:
如files:/*/a.txt等。
关于Ant路径,这里就不详述了,可以参考:javascript:void(0) ?:一个字符
*:任意0或多个字符
**:任意0或多级目录
最后看下其核心方法:
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 {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = 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)};
}
}
}