一文终结 Java 路径加载争议:斜杠 什么时候该加,什么时候不该加

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

在日常开发测试中,我们经常需要读取文件,但是文件的相对路径总是搞不清楚。要么丢了/,要么多了/,要么缺少src/main,要门测试好好的,发布到服务器上总是找不到文件......

本节将彻底搞明白这个问题,以免在使用过程中重复试错。

02 核心概念

Java中相对路径的基准是什么?

Java 中使用相对路径(如 new File("config/app.properties"))时,路径的基准不是项目根目录,不是classpath,而是 JVM 启动时所在的操作系统工作目录,即系统属性 user.dir

java 复制代码
// 打印当前工作目录,这就是相对路径的基准
System.out.println(System.getProperty("user.dir"));

03 IDE 中的相对路径

IntelliJ IDEAEclipseIDE 中运行项目时,IDE 默认将工作目录设置为项目根目录(即 pom.xmlbuild.gradle 所在目录)

示例项目结构:

css 复制代码
my-project/
├── src/
│   ├── main/
│   │   ├── java/
│   │   ├── resources/
│   │   └── image/          ← 非标准位置
│   │       ├── cropped.jpg
│   │       └── ygyz.png
│   └── test/
├── pom.xml
└── target/

示例项目结构中,image是自定义的包,属于非标准位置。在项目发布中需要特殊处理,否则无法读取文件。

IDE 中,以下代码可以工作:

java 复制代码
// IDE 中 user.dir = 项目根目录,所以能找到
File imageFile = new File("src/main/image/cropped.jpg");

⚠️ 但这只是巧合 --- 因为 IDE 恰好把 user.dir 设成了项目根目录。打包成 jar 后,这段代码就会失效。

如上面的目录结构: image 文件夹位于 src/main/image/,与 javaresources 同级。如何处理呢?会有不同的方案。

3.1 迁移到标准位置

标准位置位于src/main/resources/static下:

java 复制代码
src/main/resources/
├── static/
│   └── image/          ← 新位置
│       ├── cropped.jpg
│       └── ygyz.png
├── i18n/
├── templates/
└── application.yml

迁移后:

  • 代码中通过 classpath 读取
  • 前端可直接访问:http://localhost:8080/image/cropped.jpg
  • 打包后文件在 jar 内,部署无问题

3.2 Maven 配置

Maven配置可以保留现有的结构。

xml 复制代码
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>src/main/image</directory>
            <targetPath>static/image</targetPath>
        </resource>
    </resources>
</build>

在编译的时候,就会将文件自动打包到src/main/resources/static/image下,使用方式就可以和3.1一致了。

但是使用者会有一个疑问,明明看到src/main/resources/static/下面没有数据,只能通过target查看。

04 文件读取方法

无论是采用上面的3.13.2都是将文件统一到了一处:src/main/resources/static/image/

4.1 文件、路径读取

java 复制代码
File file = new File("src/main/resources/static/image/ygyz.jpg");
System.out.println(file.exists());
// true

Path path = Paths.get("src/main/resources/static/image/ygyz.jpg");
System.out.println(path.toFile().exists());
// true

注意:这里的前面已经不能加/,否则无法读取文件

4.2 ClassPathResource读取

这个是Spring官方的读取方式。

java 复制代码
ClassPathResource classPathResource = new ClassPathResource("static/image/ygyz.jpg");
System.out.println(classPathResource.exists());

这里默认从classpath下加载,前缀是否加/都不影响,标准模式是不加。

4.3 getResourceAsStream

java 复制代码
// 方式一:以 / 开头,从 classpath 根开始
InputStream is = getClass().getResourceAsStream("/static/image/ygyz.jpg");

// 方式二:通过类加载器,不需要 /
InputStream is = getClass().getClassLoader()
    .getResourceAsStream("static/image/ygyz.jpg");

4.4 Spring @Value 注入

java 复制代码
@Service
public class ImageService {

    @Value("classpath:static/image/cropped.jpg")
    private Resource imageResource;

    public byte[] readImage() throws IOException {
        return FileCopyUtils.copyToByteArray(imageResource.getInputStream());
    }
}

4.5 ResourceUtils

org.springframework.util.ResourceUtils

java 复制代码
File file1 = ResourceUtils.getFile("classpath:static/image/ygyz.jpg");
System.out.println(file1.exists());
// true

File file2 = ResourceUtils.getFile("src/main/resources/static/image/ygyz.jpg");
System.out.println(file2.exists());
// true

05 核心问题:/ 到底加不加?

"类要加斜杠,加载器不加"

  • 类的 getResource() / getResourceAsStream() 必须加
  • 类加载器的 getResource() / getResourceAsStream() 不用加

区别

Class.getResource() 的设计逻辑:

  • / 开头 → 从 classpath 根目录 开始查找
  • 不以 / 开头 → 从 当前类所在的包目录 开始查找
java 复制代码
// 假设当前类在 com.example.service 包下

// ✅ 从 classpath 根开始找
getClass().getResourceAsStream("/static/image/cropped.jpg");

// ❌ 从 com/example/service/ 包下开始找(找不到!)
getClass().getResourceAsStream("static/image/cropped.jpg");

ClassLoader.getResource() 的设计逻辑:

  • 永远从 classpath 根目录 开始查找
  • / 开头反而会被当作路径的一部分,导致找不到
java 复制代码
// ✅ 正确:从 classpath 根开始找
getClass().getClassLoader().getResourceAsStream("static/image/cropped.jpg");

// ❌ 错误:把 /static 当作目录名的一部分
getClass().getClassLoader().getResourceAsStream("/static/image/cropped.jpg");

06 小结

Java 相对路径以 user.dir 为基准,开发和部署环境可能不同。生产环境优先使用 classpath 加载资源,避免依赖文件系统相对路径。记住口诀:类要加斜杠,加载器不加。

相关推荐
饕餮争锋1 小时前
PATH路径列表介绍
java·linux·服务器
BS30813_vx1 小时前
31754+基于 Spring Boot + Vue 的网络文学交流分享平台设计与实现
vue.js·spring boot·后端
蛋先生DX1 小时前
内存安全翻车现场的罪魁祸首,根源就在这三步里?
c++·后端·编程语言
Rain的Java大神之路1 小时前
手写一个Spring容器
java·后端·mysql·spring·面试
小七在进步1 小时前
C++入门(1)
java·jvm·c++
IT_陈寒2 小时前
明明设了默认值,为什么我的JavaScript函数参数还是undefined?
前端·人工智能·后端
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之定制登录页
java·开发语言·spring
霸道流氓气质2 小时前
Spring AI vs Spring AI Alibaba:技术选型与平滑迁移策略
java·人工智能·spring
拽着尾巴的鱼儿2 小时前
Idea:Cannot Run Git 无法运行git
java·git·intellij-idea