1. 简介
在本文中,我们将演示如何利用 Spring 从类路径(classpath)中访问并加载文件内容的多种方式。
实际开发中,配置文件、数据文件、脚本等常常放在 resources
目录下,打包后位于 classpath 中。如何正确、稳定地读取这些资源,是每个 Java 开发者都会遇到的问题。踩过坑的人都知道,File
路径在 IDE 和 JAR 包中表现不一致,直接 new File("xxx")
必然翻车 ❌。
Spring 提供了更优雅、更通用的解决方案 —— Resource
抽象,本文带你搞懂怎么用。
2. 使用 Resource
Resource
是 Spring 对资源访问的统一抽象接口,屏蔽了底层是文件系统、JAR 包内资源,还是网络资源的差异。✅ 推荐作为标准方式使用。
获取 Resource
实例有多种方式,下面逐一介绍。
2.1 手动创建 ClassPathResource
最直接的方式是使用 ClassPathResource
:
public Resource loadEmployeesWithClassPathResource() {
return new ClassPathResource("data/employees.dat");
}
Spring 会自动选择合适的类加载器(通常是当前线程上下文类加载器),省去很多麻烦。
你也可以显式指定类加载器:
return new ClassPathResource("data/employees.dat", this.getClass().getClassLoader());
或者通过某个类来获取其类加载器:
return new ClassPathResource("data/employees.dat", Employee.class.getClassLoader());
⚠️ 注意:
ClassPathResource
构造函数中路径默认是 绝对路径(相对于 classpath 根目录)。
若需使用相对路径,可传入第二个Class
参数,路径将相对于该类所在包:
new ClassPathResource("../../../data/employees.dat", Example.class).getFile();
上面的路径就是相对于 Example.class
所在包向上回溯三级后的 data/employees.dat
。
2.2 使用 @Value 注解注入
Spring 支持通过 @Value
直接注入 Resource
对象,非常简洁:
@Value("classpath:data/resource-data.txt")
Resource resourceFile;
classpath:
前缀表示从类路径加载- 还支持
file:
(本地文件)、url:
(网络资源)等前缀
⚠️ 注意:@Value
注入是 在 Bean 初始化时完成的,属于“急加载”,如果资源不存在会直接抛异常。
2.3 使用 ResourceLoader
如果你希望延迟加载资源,或者需要动态拼接路径,推荐使用 ResourceLoader
。
先注入:
@Autowired
ResourceLoader resourceLoader;
然后按需获取资源:
public Resource loadEmployeesWithResourceLoader() {
return resourceLoader.getResource("classpath:data/employees.dat");
}
✅ 小知识:所有 ApplicationContext
实现类都实现了 ResourceLoader
接口,因此你也可以直接依赖 ApplicationContext
:
@Autowired
ApplicationContext context;
public Resource loadEmployeesWithApplicationContext() {
return context.getResource("classpath:data/employees.dat");
}
这种方式灵活性更高,适合在工具类或非 Spring Bean 中使用。
3. 警惕 ResourceUtils 的使用
Spring 提供了一个工具类 ResourceUtils
,但它的 Javadoc 明确指出:主要用于内部使用。
例如:
public File loadEmployeesWithSpringInternalClass() throws FileNotFoundException {
return ResourceUtils.getFile("classpath:data/employees.dat");
}
⚠️ 踩坑预警:ResourceUtils.getFile()
要求资源必须存在于文件系统中,无法处理 JAR 包内的资源。一旦你的应用打包成 JAR,这段代码就会抛 FileNotFoundException
。
所以,除非你 100% 确定资源在文件系统(比如外部配置文件),否则 不要用 ResourceUtils
❌。老老实实用 Resource
接口才是正道 ✅。
4. 读取资源内容
拿到 Resource
实例后,就可以读取内容了。常见方式有两种:转为 File
或获取 InputStream
。
假设我们有如下文件 data/employees.dat
存在于 classpath:
Joe Employee,Jan Employee,James T. Employee
4.1 以 File 形式读取
@Test
public void whenResourceAsFile_thenReadSuccessful() throws IOException {
File resource = new ClassPathResource("data/employees.dat").getFile();
String employees = new String(Files.readAllBytes(resource.toPath()));
assertEquals("Joe Employee,Jan Employee,James T. Employee", employees);
}
⚠️ 限制:这种方式要求资源必须在文件系统中存在。如果应用被打包成 JAR,资源在 JAR 内部,getFile()
会失败 ❌。
所以,仅适用于开发环境或资源外置的场景。
4.2 以 InputStream 形式读取
这才是生产环境的正确打开方式 ✅,适用于 JAR 包内资源:
@Test
public void whenResourceAsStream_thenReadSuccessful() throws IOException {
InputStream resource = new ClassPathResource("data/employees.dat").getInputStream();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource))) {
String employees = reader.lines().collect(Collectors.joining("\n"));
assertEquals("Joe Employee,Jan Employee,James T. Employee", employees);
}
}
✅ 优势:
- 支持 JAR 内资源
- 流式读取,内存友好
- 通用性强,推荐作为默认方式
💡 小技巧:结合
try-with-resources
确保流正确关闭。
5. 总结
方式 | 适用场景 | 是否推荐 |
---|---|---|
ClassPathResource 手动创建 |
简单直接,适合工具方法 | ✅ |
@Value("classpath:...") |
注入固定资源,配置类常用 | ✅ |
ResourceLoader / ApplicationContext |
动态路径、延迟加载 | ✅ |
ResourceUtils.getFile() |
外部文件系统资源 | ❌(慎用) |
📌 核心建议:
- 优先使用
Resource
抽象,避免路径问题 - 读取内容时优先用
getInputStream()
,兼容性最好 - 别图省事用
ResourceUtils
,上线必坑
所有示例代码已托管至 GitHub:https://github.com/example/spring-resource-demo