杰瑞科技汇

java 获取resources

核心概念:ClassLoaderClass

在 Java 中,resources 目录下的文件在编译后会被打包到最终的 JAR 或 WAR 文件的根目录下,Java 提供了两种主要的方式来加载这些资源:

java 获取resources-图1
(图片来源网络,侵删)
  1. ClassLoader.getResource():

    • 特点: 从类路径的开始查找资源,路径必须是绝对路径,且不能以 开头。
    • 示例: resources 目录下有 config.properties,路径就是 "config.properties"
  2. Class.getResource():

    • 特点: 从当前类所在的包开始查找资源,路径可以是相对路径(不以 开头)或绝对路径(以 开头,表示从类路径根开始)。
    • 示例: 如果你的类是 com.example.MyApp,想加载 com/example/config.properties,相对路径就是 "config.properties",如果想加载根目录下的 config.properties,绝对路径就是 "/config.properties"

推荐: 通常使用 ClassLoader.getResource() 更为直观和简单,因为它总是从根目录查找,与 resources 的结构完全对应。


使用 ClassLoader (最常用、最推荐)

这是最直接、最健壮的方法,适用于绝大多数情况。

java 获取resources-图2
(图片来源网络,侵删)

代码示例

import java.io.InputStream;
import java.io.IOException;
public class ResourceLoader {
    public static void main(String[] args) {
        // 1. 获取 ClassLoader
        // 使用当前类的 ClassLoader 是最稳妥的方式
        ClassLoader classLoader = ResourceLoader.class.getClassLoader();
        // 2. 定义资源路径 (相对于 resources 目录的根)
        // 假设 resources 目录下有一个文件 "my-config.json"
        String resourcePath = "my-config.json";
        // 3. 获取资源的 InputStream
        // 注意:getResource() 返回 URL,如果资源不存在会返回 null
        // getResourceAsStream() 更常用,它直接返回 InputStream,如果资源不存在则返回 null
        try (InputStream inputStream = classLoader.getResourceAsStream(resourcePath)) {
            if (inputStream == null) {
                System.err.println("资源未找到: " + resourcePath);
                return;
            }
            // 4. 读取资源内容
            // 这里我们只是简单打印一下流是否存在
            System.out.println("成功找到资源: " + resourcePath);
            System.out.println("输入流可用: " + inputStream.available());
            // 在实际应用中,你可以在这里使用 Scanner, BufferedReader 等来读取内容
            // 
            // Scanner scanner = new Scanner(inputStream, "UTF-8");
            // String content = scanner.useDelimiter("\\A").next();
            // System.out.println(content);
        } catch (IOException e) {
            System.err.println("读取资源时发生错误: " + e.getMessage());
        }
    }
}

resources 目录结构

your-project/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── ResourceLoader.java
│   │   └── resources/
│   │       ├── my-config.json
│   │       └── subfolder/
│   │           └── data.xml
│   └── test/
└── pom.xml (或 build.gradle)

使用 Class.getResource()

这种方法在某些特定场景下(资源与类在同一个包下)会很有用。

代码示例

假设 data.xml 文件与 ResourceLoader 类在同一个包下,即 src/main/resources/com/example/data.xml

import java.io.InputStream;
public class ResourceLoader {
    public static void main(String[] args) {
        // 1. 获取 Class 对象
        Class<?> clazz = ResourceLoader.class;
        // 场景 A: 使用相对路径 (从当前类所在的包开始查找)
        // 路径是 "data.xml",因为 clazz 在 com.example 包下
        String relativePath = "data.xml";
        try (InputStream inputStream = clazz.getResourceAsStream(relativePath)) {
            if (inputStream != null) {
                System.out.println("通过相对路径找到资源: " + relativePath);
            }
        }
        // 场景 B: 使用绝对路径 (从类路径根开始查找)
        // 路径必须是 "/my-config.json"
        String absolutePath = "/my-config.json";
        try (InputStream inputStream = clazz.getResourceAsStream(absolutePath)) {
            if (inputStream != null) {
                System.out.println("通过绝对路径找到资源: " + absolutePath);
            }
        }
    }
}

重要注意事项:IDE vs. 打包后的 JAR

这是最容易出错的地方,理解它们的区别至关重要。

在 IDE (如 IntelliJ IDEA, Eclipse) 中运行

  • 工作原理: IDE 会将 src/main/resources 目录直接作为类路径的一部分
  • 结果: resources 目录就像一个普通的文件夹,文件路径是真实的文件系统路径。
  • 优点: 调试方便,文件可以随时修改,程序会读取到最新的内容。

打包成 JAR 文件后运行

  • 工作原理: Maven 或 Gradle 在打包时,会将 src/main/resources 下的所有内容复制并合并到 JAR 文件的根目录中。
  • 结果: resources 中的文件现在是 JAR 文件内部的条目,而不是独立的文件。
  • 缺点: 无法直接通过文件系统路径(如 C:/project/target/my-app.jar/config.properties)来访问,必须通过 ClassLoaderClass 提供的 API 来流式读取

最佳实践: 无论在 IDE 中还是 JAR 中运行,都始终使用 ClassLoader.getResourceAsStream(),这能保证你的代码在两种环境下都能正常工作,具有最好的可移植性。

java 获取resources-图3
(图片来源网络,侵删)

常见问题与解决方案

问题1:getResourceAsStream() 返回 null

原因: 路径错误。

  1. 路径拼写错误:检查 my-config.json 是否拼成了 my_confg.json
  2. 路径层级错误resources 下有 config/app.properties,但你用了 "app.properties",应该用 "config/app.properties"
  3. 大小写敏感:文件系统可能不区分大小写,但 JAR 内部是区分的,确保路径和文件名的大小写完全一致。

解决方案:

  • 在 IDE 中调试: 在代码中打印出你尝试查找的完整路径,然后去你的 target/classes 目录下核对。
  • 检查 JAR 文件: 解开你的 JAR 包(用解压软件),检查 resources 下的文件是否在正确的位置。

问题2:路径中包含中文或特殊字符导致乱码

原因: 项目的编码设置和读取流的编码不一致。

解决方案:

  • 统一项目编码: 将整个项目的编码设置为 UTF-8(在 IDE 和 Maven/Gradle 配置中)。
  • 指定读取流的编码:
    // 不要直接使用 new InputStreamReader(inputStream)
    // 应该指定编码
    try (InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8")) {
        // 使用 reader 读取内容
    }

问题3:如何获取资源的绝对路径(文件系统路径)?

警告: 这种方法非常不推荐,因为它会破坏代码的可移植性,它只在资源作为独立文件存在时有效(例如在 IDE 中),在 JAR 包中会失败。

如果你确实需要这样做(生成一个日志文件到项目根目录)

import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
public class GetResourcePath {
    public static void main(String[] args) {
        // 获取资源的 URL
        URL resourceUrl = GetResourcePath.class.getClassLoader().getResource("my-config.json");
        if (resourceUrl == null) {
            System.err.println("资源未找到!");
            return;
        }
        try {
            // 将 URL 转换为 Path
            // toURI() 处理了空格和特殊字符
            Path resourcePath = Paths.get(resourceUrl.toURI());
            System.out.println("资源的绝对文件系统路径是: " + resourcePath.toAbsolutePath());
        } catch (URISyntaxException e) {
            System.err.println("URL 转换为 URI 失败: " + e.getMessage());
        }
    }
}

再次强调: 这段代码在 JAR 运行时会抛出 IllegalArgumentException,因为 JAR 内部的条目没有对应的文件系统路径,请优先使用 InputStream


场景 推荐方法 优点 缺点
读取资源内容 ClassLoader.getResourceAsStream(path) 最健壮、最通用,在 IDE 和 JAR 中均能工作 只能读取为流,不能直接获取文件路径
资源与类同包 Class.getResourceAsStream(relativePath) 代码语义清晰,适合与类绑定的配置 路径需要基于类的包,不如 ClassLoader 直观
调试(IDE中) Files.readAllBytes(Paths.get(...)) 可以直接读取文件内容,方便快速测试 不推荐用于生产代码,在 JAR 中会失效
需要文件路径 Paths.get(resourceUrl.toURI()) 能获取到真实文件路径 极其不推荐,破坏可移植性,在 JAR 中会失败

最终建议:

在你的日常开发中,养成使用 YourClass.class.getClassLoader().getResourceAsStream("your/file.txt") 的习惯,这是最安全、最正确的方式。

分享:
扫描分享到社交APP
上一篇
下一篇