AI多Agent协作系统实战(二十五):Spring Boot静态资源同步:为什么你改了代码但线上还是旧的?

系列第25篇 | 三个真实案例告诉你:改src≠改target,Spring Boot不背这个锅

背景

"我明明改了那个CSS文件,刷新页面怎么还是旧的?"

这句话,我在最近一周内说了至少三次。每次都是信心满满地改完代码 → 刷新页面 → 看到旧内容 → 一脸懵 → 发现忘了 mvn package

这是一个所有Spring Boot开发者都会遇到的问题,但当我们把它放进AI Agent自动开发流程中时,这个问题被放大了------Agent改完src/main/resources/static/下的文件后,理所当然地认为"改完了",但实际上浏览器加载的仍然是旧的classpath资源。

案例一:改了3个页面的CSS,线上纹丝不动

某次任务需要修改user.htmlrole.htmlpermission.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 packagemvn 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 &

这个脚本做了两件事:

  1. mvn clean package------重新编译、打包,把src/的内容同步到target/
  2. 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方式运行,必须重新打包。

经验总结

  1. 改了静态文件后必须 mvn package,只重启JVM是不够的
  2. 部署脚本是最后一道防线deploy.sh中的mvn clean package -DskipTests不能跳步
  3. 检查target/classes的时间戳:如果target比src旧,说明部署还没生效
  4. devtools只适合本地开发,对于容器化或生产环境部署,必须走完整打包流程
  5. review.py应该检查target/classes/的文件mtime,而不是只检查src/下的(这也是我们正在修复的)

三个案例有一个共同点:我们把"改了文件"等同于"生效了",但实际上改了文件只是第一步,部署才是让修改真正生效的那一步。就像你换了房间的门锁,但你的钥匙还是旧的------你需要把新钥匙配好、交到每个人手上,才算真正换了锁。

相关推荐
爱学堂IT分享4 小时前
鹤翔老师pmp项目管理培训认证考试课 (直播录播题库送学时证明)【共58课时】-51CTO学堂
后端
王的宝库4 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
码事漫谈5 小时前
类型即世界:一个 C++ 程序员的本体论入门
后端
Rain的Java大神之路5 小时前
JavaWeb开发如何解决跨域问题
java·前端·后端·nginx·web安全·面试·运维开发
IT_陈寒7 小时前
Python的多线程居然是个假把式?搞清GIL让我少熬三天夜
前端·人工智能·后端
明月_清风7 小时前
位图与布隆过滤器:海量数据下的"存在性判断"艺术
前端·后端·算法
明月_清风8 小时前
Hash 表从入门到精通:Go 实战与工程细节
前端·后端·算法
考虑考虑8 小时前
ElasticSearch索引命令
运维·后端·elasticsearch
玉宇夕落9 小时前
背单词项目一:融会贯通:从零构建"单词大师"后台——Next.js + Supabase + Drizzle 全栈开发实战
后端
橙序员小站9 小时前
从 Demo 到生产:Agent Harness 如何把 AI 真正“接”进项目
后端·aigc·ai编程