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/下的(这也是我们正在修复的)

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

相关推荐
码栈研说1 小时前
Go 语言大白话入门 12 - JSON:工作里最常见的数据格式
后端·程序员
达达尼昂1 小时前
AI Native 工程实践:如何为 Claude 5 设计更有效的上下文
android·人工智能·后端
用户2930750976691 小时前
从零搭建 AI 日记助手:Milvus 向量数据库 + RAG 实战
后端
zguigo1 小时前
结合苍穹外卖对redis进行总结和教学
后端
玉宇夕落1 小时前
Milvus向量数据库和cs bs架构学习
后端
Larcher1 小时前
别再搞混了!一文讲透 AI Workflow 和 Agent 的本质区别
javascript·人工智能·后端
瀛川1 小时前
microVM 技术解析:面向多租户 Serverless 与 AI Agent 的微虚拟机架构
后端·云原生
Conan在掘金2 小时前
鸿蒙报错速查:装对象字量禁 as Object 编译炸,根因 + 真解法
后端
IT_陈寒2 小时前
Python的finally居然不等同于Go的defer,差点坑惨我
前端·人工智能·后端