在开发校园闲置物品交易平台时,遇到了一个令人抓狂的 Bug:上传的图片明明保存在了指定目录,但前端访问时却总是 404 或 FileNotFoundException。错误日志指向了一个匪夷所思的路径:
java.io.FileNotFoundException: /tmp/tomcat.8090.17561196457261115842/
work/Tomcat/localhost/ROOT/./upload/dcb42730-0298-43f8-982f-3f37792ddbff.png
(No such file or directory)
Tomcat 为什么会跑到 /tmp/ 目录下去找上传文件?本文详细记录了问题的排查过程、根本原因和最终解决方案。
应用基于 Spring Boot 2.x + MyBatis,通过内嵌 Tomcat 运行在 localhost:8090。功能很简单:
./upload/ 目录/upload/{文件名} 访问图片上传接口正常返回了图片 URL(如 /upload/xxx.jpg),但浏览器访问该 URL 时报错,后端日志输出上述 FileNotFoundException。
项目中与文件上传相关的代码有两处:
FileUploadUtil.java@Component
public class FileUploadUtil {
@Value("${upload.dir:./upload}")
private String uploadDir;
public String upload(MultipartFile file) {
// ... 校验逻辑 ...
String filename = UUID.randomUUID() + ext;
File dest = new File(uploadDir, filename);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
return "/upload/" + filename;
}
}
WebMvcConfig.java@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:./upload/");
}
}
application.yamlupload:
dir: ./upload
spring:
mvc:
static-path-pattern: /upload/**
static-locations: file:./upload/
第一眼看过去,所有路径都统一写着 ./upload/,似乎没有任何问题。
$ ls -la ./upload/
total 24
drwxr-xr-x 2 root root 4096 ...
-rw-r--r-- 1 root root 12345 ... dcb42730-0298-43f8-982f-3f37792ddbff.png
文件确实在项目根目录的 ./upload/ 下,没有丢失。
错误信息中,Tomcat 找的是:
/tmp/tomcat.8090.17561196457261115842/work/Tomcat/localhost/ROOT/./upload/xxx.png
而我们的文件实际在:
/home/user/project/upload/xxx.png
路径完全不一致。
/tmp/tomcat.8090.xxxxx/ 是 Spring Boot 内嵌 Tomcat 运行时创建的临时工作目录。这意味着 file:./upload/ 这个相对路径,被 Tomcat 以自己的临时工作目录为基准进行了解析,而不是以项目根目录为基准。
这是典型的 相对路径解析上下文不一致 问题。
| 组件 | 路径写法 | 解析基准 | 结果 |
|---|---|---|---|
FileUploadUtil |
new File("./upload", name) |
JVM user.dir(项目根目录) |
✅ 文件保存到项目根目录下的 upload/ |
WebMvcConfig |
file:./upload/ |
Tomcat 临时工作目录 | ❌ 解析到 /tmp/tomcat.xxx/./upload/ |
application.yaml |
file:./upload/ |
Tomcat 临时工作目录 | ❌ 同上 |
Spring Boot 应用启动时,./ 这个相对路径在不同阶段指向不同的"当前目录":
new File()):以 JVM 启动时的 user.dir 为基准ResourceHandler / static-locations):以 Tomcat 自己的工作目录为基准两者不一致,导致文件写到了 A 处,读取时却从 B 处找,自然找不到。
注意:并非所有环境都会复现此问题。在部分 IDE 或直接
java -jar运行时,两者可能恰好一致,让人误以为配置没问题。一旦部署到特定环境(如通过spring-boot:run插件启动、或部署到外部 Tomcat),问题就会暴露。
利用 ${user.dir} 这个 JVM 系统属性,固定指向项目根目录,让所有组件都使用同一个绝对路径。
application.yamlupload:
dir: ${user.dir}/upload
移除与 WebMvcConfig 重复的静态资源配置(二选一即可,重复注册可能导致优先级混乱):
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
# 下面的配置已移除,由 WebMvcConfig 统一管理
# mvc:
# static-path-pattern: /upload/**
# static-locations: file:./upload/
WebMvcConfig.java注入配置属性,拼接绝对路径:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${upload.dir}")
private String uploadDir;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + uploadDir + "/");
}
}
FileUploadUtil.java 无需修改它已经在读取 @Value("${upload.dir}"),application.yaml 改为绝对路径后自动生效。
修复后,三个组件都使用同一个绝对路径 ${user.dir}/upload,无论 Tomcat 的工作目录是什么,文件写入和读取都指向同一个位置:
# user.dir = /home/user/project
# upload.dir = /home/user/project/upload
FileUploadUtil 保存 → /home/user/project/upload/xxx.png ✅WebMvcConfig 读取 → file:/home/user/project/upload/ ✅http://localhost:8090/upload/xxx.png ✅./ 相对路径 — 不同组件的解析基准可能不同,应使用 ${user.dir} 或硬编码绝对路径spring.mvc.static-locations 和自定义 WebMvcConfigurer.addResourceHandlers 功能重叠,选其一即可,否则容易互相干扰${user.dir}、${java.home} 等系统属性可以在 application.yaml 中直接使用,非常方便一个 ./ 引发的 Bug,排查起来说难不难,说简单也花了大半天。归根结底是对 Spring Boot 内部路径解析机制的理解不够深入。如果你也遇到了类似的"文件保存了但访问不到"的问题,不妨检查一下项目中是否存在四处散落的相对路径 — 把它们统一改为绝对路径,问题很可能就迎刃而解了。
本文发布于 2026-06-27