CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / JHIPSTER / P-219 · JHipster 项目开发

JHipster 开发 04:开发工作流——热重载与前后端联调

技术栈认识完了,进入日常开发。JHipster 的开发工作流是”双进程”模式:后端 Spring Boot 跑在 8080,前端 Vite dev server 跑在 9000,两者各自热重载、通过代理打通。这个模式我们用了五年,把它吃透后日常迭代速度非常快。这篇把机制和坑一次讲完。

双进程模型

先建立全局认知:

flowchart LR
  B["浏览器<br/>http://localhost:9000"] -->|"页面资源"| V["Vite dev server<br/>:9000"]
  B -->|"/api/** 请求"| V
  V -->|"代理转发"| S["Spring Boot<br/>:8080"]
  S --> DB[("H2 / 数据库")]
  subgraph 热重载
    V -.->|"改 ts/html → 秒级刷新"| B
    S -.->|"改 java → 自动重启"| S
  end

关键在于:浏览器永远只访问 9000,/api 请求由 Vite 代理转发给 8080。因此开发期不存在跨域问题——请求与页面同源,这正是代理存在的意义。直接访问 8080 反而不行,那上面没有前端页面。

日常两个终端:

# 终端 1:后端
./mvnw

# 终端 2:前端
npm start

后端热重载:Spring Boot DevTools

JHipster dev profile 默认集成 Spring Boot DevTools,监听 classpath 变化自动重启:

  • IDE 里改 Java 文件,保存触发增量编译(IDEA 的”自动构建”、Eclipse 的自动编译),DevTools 检测到 class 变化即重启;
  • 重启是半热的:容器快速重建,比冷启动快得多,但会丢掉应用内状态(比如你手填的内存数据);
  • application-dev.yml 等资源文件同样触发。

两个实用细节。第一,DevTools 默认排除了哪些目录、要不要触发重启,可以在 application.ymlspring.devtools.restart.* 下微调。第二,IDEA 用户需要开启 Build project automatically 并勾选允许运行中编译(Registry 的 idea.auto.make 一族设置),否则保存不会触发编译,DevTools 自然无感。

我们的习惯:改业务代码靠 DevTools 足够;涉及依赖变更(pom.xml)或 JDL 重新生成,还是老老实实 Ctrl+C 重启。

前端热重载:Vite

npm start 起的是 Vite dev server(9000 端口)。Angular 21 的组件改模板、改类,保存后毫秒到秒级完成热更新(HMR),页面状态尽量保留。代理配置在 vite.config.mts(生成时已写好):

export default defineConfig({
  server: {
    port: 9000,
    proxy: {
      '/api': { target: 'http://127.0.0.1:8080', changeOrigin: true },
      // OIDC/Keycloak 等回调路径同理代理
    },
  },
});

如果后端端口改了,记得同步改这里,否则表现就是”页面能开,一登录就 502”。

浏览器无痕窗口

一个不起眼但极省时间的小技巧:调试 JHipster 前端时开无痕窗口。原因有两个:

  1. 前端资源带强缓存策略,改了页面却看到旧版本,八成是缓存作祟;无痕窗口每次干净的缓存,结果是确定性的。
  2. 认证状态存在 localStorage(JWT)里,admin/user 多账号切换测试权限时,无痕窗口天然隔离,不用手动清存储。

我们团队的规矩:复现”我这里不对”的前端问题,先在无痕窗口验一遍再说。

日志排障

双进程意味着日志也在两处:

  • 后端日志直接打在 ./mvnw 的终端里;SQL 日志在 dev profile 默认可见(logging.level.org.hibernate.SQL=debug 可在 application-dev.yml 调),排查”为什么这条 SQL 不对”很方便;
  • 如果配置了日志文件输出,tail -f 跟踪:
tail -f ./target/logs/*.log        # 后端
# 前端编译报错看 npm start 终端,构建失败会直接贴出错误位置

Liquibase 在启动时的执行日志也值得扫一眼——每个 changeset 的执行结果都会列出来,数据库没升上去第一现场就在这里(第 7 篇)。

IDE 与双进程调试

两个进程意味着两套调试入口,配置一次受益长期:

  • 后端:IDEA 里直接跑应用主类(Application.java)替代 ./mvnw,断点、条件断点、表达式求值全部可用;DevTools 与 IDE 运行兼容,重启照旧自动;
  • 前端:Vite 默认产出 source map,浏览器 DevTools 的 Sources 面板里直接对 TS 源码下断点,配合无痕窗口排障效率很高;
  • 网络层:DevTools 的 Network 面板按 Fetch/XHR 过滤,重点盯三样——请求是否带了 Authorization: Bearer 头(认证问题第 8 篇)、响应是 4xx 还是 5xx(前端问题还是后端问题)、payload 分页参数是否正确。

H2 Console:直接看数据

dev 环境的 H2 带网页控制台,查数据比写测试快得多。application-dev.yml 里默认开启 spring.h2.console.enabled=true,通过 /h2-console(走 8080)登录,JDBC URL 在启动日志里打印。排查”数据到底存成什么样了""Liquibase 建的表长什么样”时,这是第一现场。想玩真 SQL 方言,等 CI 里 Testcontainers 的 PostgreSQL(第 17 篇)。

常见联调问题速查表

症状原因解法
页面能开,接口全 401/404 或 502Vite 代理目标与后端实际端口不一致对齐 vite.config.mts 与后端端口
CORS 报错(开发期)绕过了代理,前端直接打了 8080 或其他域检查代码里写死的 URL,一律走相对路径 /api
8080 被占用上一个后端没退干净lsof -i :8080 找到进程杀掉,或改端口
9000 被占用多个前端实例同上;JHipster 会提示端口占用
改了 Java 没生效IDE 没开自动编译,DevTools 无触发开自动构建,或手动 Build
改了前端”没变”浏览器缓存无痕窗口 / 硬刷新
登录后立即 401JWT 过期时间与系统时钟不一致(WSL2 时钟漂移常见)同步系统时间
改了实体但接口返回旧结构后端没重启(依赖变更级别的修改 DevTools 不触发)手动重启后端进程

特别展开一下 CORS:开发期不应看到 CORS。同源走代理是设计内状态;如果控制台出现 CORS 报错,说明有人写了绝对地址(http://localhost:8080/api/...),把它改回相对路径即可。生产期前后端同 jar 部署也不存在跨域;只有前后端分开部署时才需要在 application-cors.yml(Spring Boot 4 的 CORS configuration properties)里显式放行域名。

一个完整的迭代循环

以”给 Customer 加一个字段并出现在页面”为例,我们的日常节奏:

  1. JDL 加字段,jhipster jdl app.jdl(或 jhipster entity Customer --single-entity)——后端、DTO、Liquibase changeset、前端模型、测试全部更新(第 6 篇);
  2. DevTools 自动重启,Liquibase 自动执行新 changeset;
  3. 前端页面细节手工微调,Vite 秒级热更;
  4. 无痕窗口验证 admin/user 两种视角;
  5. ./mvnw verify 跑测试后提交。

整个循环通常几分钟,这正是 JHipster 双进程工作流的甜点。

联调前自检清单

遇到联调问题时按这个顺序过一遍,比随机尝试快得多:

  1. 两个终端都活着:8080 后端日志正常滚动、9000 前端无编译报错;
  2. 浏览器地址是 localhost:9000 而不是 8080
  3. 请求路径以 /api 开头(走代理)而非绝对地址;
  4. Network 面板确认请求到达的是 Vite(端口 9000)而不是直连后端;
  5. 后端终端里能看到对应请求的日志(到了后端就是后端问题,没到就是代理问题);
  6. 认证问题先看 Authorization 头是否存在与 token 是否过期;
  7. 仍无解:无痕窗口 + 重启两个进程,排除缓存与僵尸状态。

小结

  • 双进程:./mvnw(8080)+ npm start(9000),浏览器只访问 9000,/api 由 Vite 代理转发。
  • 后端靠 Spring Boot DevTools 热重启(IDEA 记得开自动编译),前端靠 Vite HMR 秒级生效。
  • 无痕窗口规避缓存与账号状态污染,是前端排障第一动作。
  • 开发期出现 CORS 即代码写了绝对地址的信号;端口冲突查 lsof;日志分看两个终端。

系列导航

← 集合源码 004:Stack & Queue 源码解析 目录 网络协议 004:网络基础(3):IP相关协议详解 →
← 返回文章列表