系列第25篇 | 三个真实案例告诉你:改src≠改target,Spring Boot不背这个锅
背景
"我明明改了那个CSS文件,刷新页面怎么还是旧的?"
这句话,我在最近一周内说了至少三次。每次都是信心满满地改完代码 → 刷新页面 → 看到旧内容 → 一脸懵 → 发现忘了 mvn package。
这是一个所有Spring Boot开发者都会遇到的问题,但当我们把它放进AI Agent自动开发流程中时,这个问题被放大了------Agent改完src/main/resources/static/下的文件后,理所当然地认为"改完了",但实际上浏览器加载的仍然是旧的classpath资源。
案例一:改了3个页面的CSS,线上纹丝不动
某次任务需要修改user.html、role.html、permission.html三个页面的样式。开发者(无论是人还是AI)修改了src/main/resources/static/下的源文件,然后自信满满地刷新了http://localhost:8080/user.html------结果页面一点变化都没有。
为什么?
Spring Boot在开发模式下,静态资源的加载路径和大多数人想的不一样。当你的项目结构是:
scss
gainch-service/
├── src/main/resources/static/
│ ├── user.html
│ └── css/
└── target/classes/
└── static/
├── user.html
└── css/
运行时,Spring Boot读取的是**target/classes/static/**,而不是你刚刚修改的src/main/resources/static/。这两个目录在mvn package或mvn compile时会同步一次,但在这之后,你对src做的任何修改都不会自动同步到target。
案例二:Agent打包部署的跳步问题
在我们的AI Agent自动开发流程中,小虾(开发Agent)完成代码修改后,需要执行deploy.sh来打包部署:
bash
#!/bin/bash
cd /vol1/1000/workspace/file/8080/gainch-service
mvn clean package -DskipTests -q
java -jar target/gainch-service-1.0.0.jar &
这个脚本做了两件事:
mvn clean package------重新编译、打包,把src/的内容同步到target/java -jar------用新JAR包启动服务
有一次,小虾修改完代码后跳过了部署步骤 ,直接在回复中说"开发完成"。review.py检查时发现src/下的文件确实修改了(文件mtime更新了),但线上访问页面仍然是旧的------因为target/classes/里的还是旧文件。
这个问题被review.py的mtime检查捕获到,但因为review.py检查的是src/main/resources/static/下的文件mtime,而不是target/classes/的,所以误判为"修改已生效"。
案例三:target/classes残留导致的幽灵修改
还有一个更隐蔽的问题:当你修改了src/下的文件,然后手动静止了Java进程,用java -jar重新启动(跳过mvn package),这时Spring Boot会直接使用target/classes/中的旧文件。
为什么?因为java -jar target/gainch-service-1.0.0.jar加载的是JAR包内的classpath资源,而JAR包是在上次mvn package时构建的。如果你只重启JVM而不重新打包,JAR包里的静态资源文件永远是老的。
有一次我连续改了三次CSS,每次都重启了Java进程,但页面始终没变。排查了十分钟才发现:上次mvn package失败(测试没通过),target/gainch-service-1.0.0.jar是更早之前的版本。我一直在用旧JAR包重启,改再多src文件也没用。
根因分析:Spring Boot的classpath机制
Spring Boot的静态资源加载路径是这样的:
bash
请求 /user.html
↓
Spring DispatcherServlet
↓
ResourceHttpRequestHandler
↓
classpath:/static/ (即 target/classes/static/)
↓
返回 target/classes/static/user.html
关键点:src/main/resources/是源文件目录,target/classes/是编译输出目录。Spring Boot运行时只认target,不认src。
scss
mvn package / mvn compile
↓
src/main/resources/ ────────────→ target/classes/
(源文件) 同步 (运行时加载)
这不是Bug,是Spring Boot的标准设计。只有当你开启了spring-boot-devtools的LiveReload或者使用了spring.thymeleaf.cache=false(针对模板引擎)时,才会在开发模式下热加载静态资源。但对于生产部署或java -jar方式运行,必须重新打包。
经验总结
- 改了静态文件后必须
mvn package,只重启JVM是不够的 - 部署脚本是最后一道防线 :
deploy.sh中的mvn clean package -DskipTests不能跳步 - 检查target/classes的时间戳:如果target比src旧,说明部署还没生效
- devtools只适合本地开发,对于容器化或生产环境部署,必须走完整打包流程
- review.py应该检查target/classes/的文件mtime,而不是只检查src/下的(这也是我们正在修复的)
三个案例有一个共同点:我们把"改了文件"等同于"生效了",但实际上改了文件只是第一步,部署才是让修改真正生效的那一步。就像你换了房间的门锁,但你的钥匙还是旧的------你需要把新钥匙配好、交到每个人手上,才算真正换了锁。