0%

Spring Boot读取 Resource 资源文件

整理下 Spring 项目中读取 resources 下的文件的方式,包括 Resource 接口,另外跟一下 ResourceUtils 读取资源文件的流程。

File 方式读取

直接使用 File 类的接口读取资源文件,需要注意的是传入应是相对路径,生成的项目文件中资源文件的路径可以参考 target 目录下的结构。比如,资源文件目录下的文件 resources/static/test.html,在生成的 target 目录下路径是:target/classes/static/test.html

所以可以用以下方式访问:

1
2

File f = new File("target/classes/static/test.html");

要注意,使用相对路径,使用绝对路径(“/”代表根目录)

1
/target/classes/static/test.html

或者

1
classes/static/test.html

会 “file not found”。

Resource 接口与 ResourceLoader

Resource 接口抽象了资源文件,提供了 exists()、isFile()、getFile()、getURL() 等方法,并从 InputStreamSource 接口继承了 getInputStream() 方法。InputStreamSource 接口的 getInputStream() 方法保证多次调用此方法时,每次得到的是一个”fresh”的输入流。Resource 接口有对应各种类型资源的实现类,如 ClassPathResource、FileSystemResource、UrlResource 等。

ResourceLoader 接口中定义了 getResource(String location) 方法,用于获取一个 Resource,并且规定其实现需要支持如”file:C:/test.dat”、”classpath:test.dat”这样的前缀的 URL。另,一般其实现类 ApplicationContext 支持使用如”WEB-INF/test.dat”的相对路径读取文件。

getResource(String location) 不返回 null,对于返回的 Resource 对象,可以使用 exists() 判断资源是否存在。

ResourceLoader 接口的实现有:

  • DefaultResourceLoader,子类包括 ClassRelativeResourceLoader、FileSystemResourceLoader、ServletContextResourceLoader等;

  • ResourcePatternResolver 接口,实现中需要关注的是 ApplicationContext 接口。

当使用了某种协议前缀时,ResourceLoader 返回相应的 Resource 的实现对象,或者不指定时,将根据使用的 ApplicationContext 决定,这是策略模式的一个应用。

注册一个 ResourceLoaderAware 接口的实现类,可以从其回调 setResourceLoader(ResourceLoader resourceLoader) 中得到 ResourceLoader 实例。

使用如下代码可以读取资源文件:

1
2
3
4
5
Resource r = ApplicationContextUtil.getApplicationContext().getResource("classpath:static/test.html");
// 使用 "file:" 协议也可以读取
// Resource r = ApplicationContextUtil.getApplicationContext().getResource("file:target/classes/static/test.html");

File file = r.getFile();

其中,ApplicationContextUtil 实现了 ApplicationContextAware 接口,来获取 ApplicationContext。

使用 Spring 提供的 ResourceUtils 读取资源文件

ResourceUtils 提供了 getFile(URL)、getFile(String)方法。但是,类的文档中说明,这一类主要在框架内使用,开发者读取资源文件建议使用的是 ResourceLoader 和 Resource 接口。使用如下:

1
File file = ResourceUtils.getFile("classpath:static/test.html");

或者

1
File file = ResourceUtils.getFile("file:target/classes/static/test.html");

或者

1
File file = ResourceUtils.getFile("target/classes/static/test.html");

使用”classpath:”协议和”file:”协议或者直接作为文件路径时,也要使用相对路径。

getFile(String) 方法对以上三种方式,其实现 DefaultResourceLoader 中的实现基本一致,可以看下其代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static File getFile(String resourceLocation) throws FileNotFoundException {
Assert.notNull(resourceLocation, "Resource location must not be null");
if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
String description = "class path resource [" + path + "]";
ClassLoader cl = ClassUtils.getDefaultClassLoader();
URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
if (url == null) {
throw new FileNotFoundException(description +
" cannot be resolved to absolute file path because it does not exist");
}
return getFile(url, description);
}
try {
// try URL
return getFile(new URL(resourceLocation));
}
catch (MalformedURLException ex) {
// no URL -> treat as file path
return new File(resourceLocation);
}
}

首先检查参数 resourceLocation 是否是使用了”classpath:”的方式,使用了这种方式,使用 ClassLoader 类的 getResource(String) 方法读取文件。

否则尝试将作为其他协议的 URL 读取,如果使用的是”file:”之外的其他协议的 URL,抛出 FileNotFoundException 异常;上文使用”file:”的方式即作为”file:”协议的情况处理,提取 URL 中的文件路径初始化 File 对象。

最后,如果在尝试将 resourceLocation 解析为 URL 时失败,即不是 URL 的情况,URL构造器会抛出 MalformedURLException 异常,此时将尝试把 resourceLocation 作为文件路径读取。

打包为 jar 时的资源读取

以上示例,是在 IDE 中执行代码时的情况,当把项目打包成 jar 包时,在文件系统中无法找到相应文件,会出现 FileNotFoundException 异常。此时可以使用 InputStreamSource 接口的 getInputStream() 方法来读取资源文件。

代码如下:

1
2
Resource r = ApplicationContextUtil.getApplicationContext().getResource("classpath:static/test.html");
InputStream in = r.getInputStream();