
图源:
Resource
Spring中的资源被抽象为一个Resource接口:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
Resource 接口中最重要的一些方法是。
-
getInputStream(): 定位并打开资源,返回一个用于读取资源的InputStream。我们期望每次调用都能返回一个新的InputStream。关闭该流是调用者的责任。 -
exists(): 返回一个boolean值,表示该资源是否以物理形式实际存在。 -
isOpen(): 返回一个boolean,表示该资源是否代表一个具有开放流的句柄。如果为true,InputStream不能被多次读取,必须只读一次,然后关闭以避免资源泄漏。对于所有通常的资源实现,除了InputStreamResource之外,返回false。 -
getDescription(): 返回该资源的描述,用于处理该资源时的错误输出。这通常是全路径的文件名或资源的实际URL。
内置的Resource实现
Spring内置了一些Resource接口的实现类:
这里介绍几个常见的Resource实现类。
ClassPathResource
ClassPathResource是最常见的,通过它我们可以访问ClassPath中的文件。
举例说明,假如在Spring的静态资源目录resources下有一个文件override.properties:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
可以通过下面的示例代码将其内容打印到控制台:
package com.example.resource.controller;
// ...
("/hello")
public class HelloController {
("")
public String hello() throws IOException {
Resource resource = new ClassPathResource("override.properties");
printContent(resource);
return Result.success().toString();
}
private void printContent(Resource resource) throws IOException {
File file;
try {
file = resource.getFile();
} catch (FileNotFoundException e) {
String content = resource.getContentAsString(StandardCharsets.UTF_8);
System.out.println(content);
return;
}
printContent(file);
}
private void printContent(File file) throws IOException {
FileReader fr;
fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
do {
line = br.readLine();
if (line == null) {
break;
}
System.out.println(line);
}
while (true);
br.close();
}
}
这其中下面这行代码,明确创建了一个到resources/override.properties文件的资源:
Resource resource = new ClassPathResource("override.properties");
如果Spring项目是通过IDE运行的,那么resources这个静态资源目录会被加入ClassPath,因此自然可以通过ClassPathResource正确访问到,如果项目是打包成Jar包运行,该目录同样会被打包的Jar包中的/BOOT-INF/classes目录:

该目录同样会被加入ClassPath中,所以同样可以获取到正确的文件。
正是因为这样的特性,所以在开发中通常都会用ClassPath的方式引用静态资源,而非文件路径。因为后者可能导致部署的目标服务器上缺少相应的资源而出错。
FileSystemResource
FileSystemResource是通过文件系统来访问资源,具体来说就是文件的相对路径和绝对路径。
同样是上面的示例,只需要稍微修改:
// ...
("")
public String hello() throws IOException {
Resource resource = new FileSystemResource("src/main/resources/override.properties");
printContent(resource);
return Result.success().toString();
}
// ...
当然也可以使用绝对路径:
Resource resource = new FileSystemResource("D:/workspace/learn_spring_boot/ch28/resource/src/main/resources/override.properties");
UrlResource
通过UrlResource可以访问用URL定义的资源,当然最常见的是网络资源:
Resource resource = new UrlResource("https://blog.icexmoon.cn/");
要说明的是,通过UrlResource创建的Resource,是无法通过调用Resource.getFile()方法获取文件的,会产生FileNotFoundException异常。因此只能是以Resource.getInputStream()方法获取输入流,然后再打印内容。不过Resource接口其实已经提供了一个getContentAsString()方法:
public interface Resource extends InputStreamSource {
// ...
default String getContentAsString(Charset charset) throws IOException {
return FileCopyUtils.copyToString(new InputStreamReader(this.getInputStream(), charset));
}
// ...
}
URL实际上也可以指定本地文件系统:
Resource resource = new UrlResource("file://D:/workspace/learn_spring_boot/ch28/resource/src/main/resources/override.properties");
其它的Resource实现可以阅读。
ResourceLoader 接口
可以通过接口ResourceLoader来获取Resource:
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有的application context都实现了这个接口,可以当做ResourceLoader来使用:
("/hello")
public class HelloController {
private ApplicationContext ctx;
("")
public String hello() throws IOException {
Resource resource = ctx.getResource("classpath:override.properties");
printContent(resource);
return Result.success().toString();
}
}
实际上在这个示例中,直接注入ResourceLoader更为合适:
("/hello")
public class HelloController {
private ResourceLoader resourceLoader;
("")
public String hello() throws IOException {
Resource resource = resourceLoader.getResource("classpath:override.properties");
printContent(resource);
return Result.success().toString();
}
// ...
}
作为参数传入getResource方法的classpath:override.properties这样的被称作资源字符串:
| 前缀 | 示例 | 说明 |
|---|---|---|
| classpath: | classpath:com/myapp/config.xml |
从classpath加载。 |
| file: | file:///data/config.xml |
作为 URL 从文件系统加载。另请参见. |
| https: | https://myserver/logo.png |
以 URL 形式加载。 |
| (none) | /data/config.xml |
取决于底层的 `ApplicationContext'。 |
如果资源字符串不带前缀(比如classpath:),ResourceLoader获取资源的行为根据ApplicationContext的类型的不同而不同,比如ClassPathXmlApplicationContext默认会以ClassPathResource的方式获取资源,FileSystemXmlApplicationContext默认会以FileSystemResource的方式获取资源。
ResourcePatternResolver 接口
ResourcePatternResolver接口是ResourceLoader的扩展:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
在ResourceLoader的基础上,getResources()方法增加了对通配符的支持:
("/hello")
public class HelloController {
private ResourcePatternResolver resourcePatternResolver;
("")
public String hello() throws IOException {
Resource[] resources = resourcePatternResolver.getResources("classpath:*.properties");
for(Resource r: resources){
System.out.println(r.getFilename());
}
if (resources == null || resources.length == 0){
return Result.fail("没有获取到文件").toString();
}
Resource resource = resources[0];
printContent(resource);
return Result.success().toString();
}
// ...
}
这个示例中,resourcePatternResolver.getResources("classpath:*.properties")可以匹配到resource目录下所有以.properties为后缀的文件作为资源对象返回。
如果需要从多个jar中检索同样的包名下的资源,可以使用
classpath*:这样的前缀配合通配符检索。
ResourceLoaderAware 接口
可以让bean通过实现ResourceLoaderAware接口的方式获取ResourceLoader:
("/hello")
public class HelloController implements ResourceLoaderAware {
// ...
public void setResourceLoader(ResourceLoader resourceLoader) {
Resource resource = resourceLoader.getResource("classpath:override.properties");
try {
printContent(resource);
} catch (IOException e) {
e.printStackTrace();
}
}
}
当然,相比直接注入ResourceLoader,这样做并没有什么优势。
注入Resource
可以借助@Value注解直接注入Resource:
("/hello")
public class HelloController implements ResourceLoaderAware {
// ...
("${my.properties}")
private Resource resource;
("")
public String hello() throws IOException {
printContent(resource);
return Result.success().toString();
}
// ...
}
Spring Boot默认的配置文件application.properties:
my.properties=classpath:override.properties
当然,通过setter或构造器注入也是可以的,这里不再演示。
资源字符串中使用了通配符,可以注入所有匹配的资源:
my.all.properties=classpath:*.properties
("/hello")
public class HelloController implements ResourceLoaderAware {
// ...
("${my.all.properties}")
private Resource[] resources;
("")
public String hello() throws IOException {
for (Resource r : resources) {
System.out.println(r.getFilename());
}
if (resources == null || resources.length == 0) {
return Result.fail("没有获取到文件").toString();
}
Resource resource = resources[0];
printContent(resource);
return Result.success().toString();
}
// ...
}
本文所有的示例代码可以通过获取。
谢谢阅读。
参考资料

文章评论