关注我的公众号:【编程朝花夕拾】,可获取首发内容。
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 IDEA、Eclipse 等 IDE 中运行项目时,IDE 默认将工作目录设置为项目根目录(即 pom.xml 或 build.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/,与 java、resources 同级。如何处理呢?会有不同的方案。
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.1和3.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 加载资源,避免依赖文件系统相对路径。记住口诀:类要加斜杠,加载器不加。