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


原始标题:Access a File from the Classpath using Spring | Baeldung