健康接口返回了 ready,采购系统就能交付了吗

一个开发者拿到采购审批应用,第一件事通常是运行服务。如果浏览器显示 ready,容易把它理解成“系统已经启动”。可是采购申请能否提交、订单是否只生成一次、其他租户能否读取申请,都没有经过这个接口。这里需要先把“从空目录运行”拆成可检验的阶段:命令行能编译源码;服务器能加载单个 WAR;HTTP 能抵达健康资源;业务事务能提交;权限和业务结果能被核对。第 00 章只走到前三项,不用健康响应冒充后面三项的证明。

本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。

教学对象是单 WAR 的企业采购系统。实际采购还需要申请明细、审批记录、订单、通知和审计;本章只检验健康接口。当前工程另有未认证的 /api/db-check 诊断入口,且在 JAVAEE_DEMO_MODE=true 时才开放未认证的 /api/lab/requests 教学入口;二者仅供监听回环地址的隔离实验使用,不能作为生产服务对外暴露。先修自测也因此很具体:能解释 HTTP 200 与响应体的区别,能从 Maven 的失败退出码定位编译或依赖问题,知道 Java 的 try 资源关闭和 SQL 提交并不是一回事。不会 Servlet 或 CDI 不妨碍完成这一章;想验证数据库提交,则须等到后续数据与事务章节准备好隔离数据库和判据。

试着把三个常见问题分别提给值班同事:“构建完成了吗”“应用部署成功了吗”“用户能提交采购申请吗”。第一项要求构建命令、目标包和退出码;第二项要求服务器确实加载了这份包及其上下文根;第三项需要业务入口、真实事务、输入与最终记录。如果只交出一次浏览器截图,连截图对应哪一份 WAR 都无法追溯,更谈不上第三项。把证据按操作步骤归档,是为了让失败时知道回退到哪一个可复现的阶段。

环境版本与“兼容”各指什么

本例采用 JDK 21、Jakarta EE 11 Platform API、Open Liberty 26.0.0.5、Maven Wrapper 3.9.9、PostgreSQL 16.15 和 pgJDBC 42.7.7。这是一组教学工程选型,不是 Java EE 的唯一组合;Jakarta EE 11 平台的最低 Java SE 版本是 17,不能从本例的编译目标 21 反推最低要求。实际构建前记录 java -version、Maven 版本、操作系统架构、服务器版本、部署包 SHA-256 和数据库版本;否则“同一份代码”可能在不同编译器或服务器配置下得到不同结果。PostgreSQL 和驱动在部署配置中已声明,但健康接口没有申请数据库连接,不能由它的响应证明数据库可用。

名称也有一道容易踩的边界。Java EE 8 与 Jakarta EE 8 的企业 API 仍使用 javax.*;Jakarta EE 9 起相应 API 转为 jakarta.*。本工程的 REST 注解来自 jakarta.ws.rs,但 JDBC 的 javax.sql.DataSource 属于 Java SE 的 javax.sql,绝不能把工程里的 javax 无差别替换。采用 Jakarta EE 11 API 编译,不等于一份旧的 javax.servlet WAR 可在此直接运行。引用历史平台时先看版本,不能仅凭名字判断包名或二进制兼容性。

Platform、Web Profile、Core Profile 也不是三个不同的编译模式。它们规定运行时必须提供的能力范围:完整平台包含比 Web Profile 更多的企业组件;一个实现了 Servlet 的服务器不自动等于 Jakarta EE 11 Platform。选择 Open Liberty 时,要分别核对对应版本的官方兼容范围、配置实际启用的特性和本机部署结果。当前 server.xml 声明 jakartaee-11.0 与 jdbc-4.3;声明特性说明准备使用什么,不说明受管 DataSource 已建立连接,更不说明采购规则已被执行。Tomcat 提供 Web 技术子集的事实也不能替代完整平台兼容结论。

因此版本冻结要记录两张表,不能只写“Jakarta EE 11”。第一张是规范要求:平台版本、相关技术版本及其 JDK 最低要求;第二张是实际执行:哪一个应用服务器二进制、启用的特性、JDK 与数据库驱动、部署的 WAR 摘要。前者回答“规范承诺什么”,后者回答“这次实验运行了什么”。同一服务器可能启用不同特性;升级补丁版也可能改变故障日志或默认配置。缺了第二张表,就无法区分产品兼容声明与本章自己的观察记录。

最小部署链从哪里进入

在 工程根 pom.xml 可见四个模块:domain、application、adapters-jdbc、webapp。这一章只沿编译、打包与健康访问路径阅读,不把模块的存在等同于其功能已被外部 HTTP 暴露。webapp/pom.xml 将 Web 层打成 WAR;平台 API 以服务器提供的依赖参与编译,防止应用把另一套 API 实现塞进 WAR 造成类加载冲突。实际包内类及库仍应以 jar tf 检查,而不是只读 pom 作结论。

HTTP 路径有三个独立部分。RestApplication.java 的 @ApplicationPath("/api") 提供 REST 应用前缀;HealthResource.java 的 @Path("/health")、@GET 和 @Produces(TEXT_PLAIN) 定义资源;部署文件设 contextRoot="/procurement"。因此目标地址是 /procurement/api/health,不是仅 /health。方法返回的字面字符串为 ready。它不查数据库、不取得当前用户、不调用采购用例,也没有对库存或下游连接做诊断;即便配置了数据库资源,单看代码也没有可证明的业务读写。

把最小链路画成四个节点就够了:客户端请求 → Web 上下文根 → REST 应用路径 → 健康资源。若最后收到响应,只能在本次请求和该部署配置下断定网络、服务器、路由与资源方法完成了对应路径。若返回 404,先逐项检查上下文根、REST 前缀和资源名;若连接被拒绝,先查进程及监听端口;若请求到达服务器却报 500,再看该请求的服务器日志。不要在 404 时修改业务状态机,也不要把 500 直接归因于数据库:这个入口没有查库。

工程同时有一个独立的领域入口,帮助读者理解“代码可检验”与“业务可部署”的区别:RequestState.java 列出 DRAFT、SUBMITTED、APPROVED、REJECTED、ORDERED,而 ProcurementRulesTest.java 检查金额、合法和非法转换。它能在不启动容器的情况下断言纯 Java 规则;它不是 REST 调用,更没有证明数据库、用户身份或订单的端到端结果。领域约束怎样转成可检查的采购需求,留到第 01 章具体定义。

provided 只影响应用打包的依赖范围,不负责替读者安装服务器。WAR 不是可以像普通命令行程序那样用 java -jar 单独运行的业务进程;Servlet 和 REST 资源的路由由已配置的运行时建立。单独编译 HealthResource 也无法验证部署路径,因为上下文根在服务器配置、应用前缀在另一个 Java 类、资源相对路径在这个类自身。三处定义发生在不同层,必须合起来检查。反过来,若 WAR 包里意外混入与运行时冲突的 API 包,应先检查依赖作用域与实际包内容,而不是改资源方法让它返回不同文字。

这也说明为什么从“可以编译”走到“可以交付”不是换一条命令。对领域测试而言,DRAFT 直接转到 APPROVED 被拒绝是期望的成功断言;对健康接口而言,ready 只是成功响应的正文;对采购结果而言,成功必须把申请状态、版本和订单行放在同一业务语境里读。三个输出都可能呈现绿色,但绿色各有自己的断言对象。不要把单元测试的绿色归功于应用服务器,也不要因为服务端能返回一个字符串就认为状态机已经按用户身份执行。

构建、部署、观测必须分开留证据

从仓库根目录使用已有 Maven Wrapper 构建;这里的路径相对于仓库根目录,不要求修改全局 Maven。以下是复跑命令,已保存的运行结果在后文单独说明:

1
2
3
cd examples/javaee-enterprise
../hibernate-lab/mvnw -B -ntp -f "$PWD/pom.xml" clean verify
jar tf webapp/target/procurement-webapp-1.0-SNAPSHOT.war | grep -E 'WEB-INF/classes/blog/javaee/web/(HealthResource|RestApplication)\.class'

构建命令退出 0 只能证明当前环境的编译、打包和测试阶段没有报告失败;列出两段类路径只能证明 WAR 中存在类,不证明服务器已将其注册为 REST 资源。要真正调用 HTTP,先选用隔离实验服务器与专用数据库 javaee_lab,照 README.md 准备本地角色、迁移及受管资源;JAVAEE_LAB_PASSWORD 只在本地环境提供,不写入仓库、日志或截图。下载运行时须核对所用发布物的官方摘要,并保留实际版本与配置。不要把本章当成允许对生产库执行迁移的操作手册。

部署用 deploy/server.xml 指向生成的 WAR,配置 JAVAEE_PORT=9085、JAVAEE_WAR 的绝对路径、JAVAEE_DRIVER_DIR 及本地数据库账号,再启动对应 Open Liberty 服务器。具体路径和临时凭证由操作者在隔离环境填入,不能直接复制带 /path/to/wlp 的占位符运行。scenarios/00-health.sh 用 curl -fsS --max-time 10 访问 http://127.0.0.1:${JAVAEE_PORT}/procurement/api/health 并检查响应体等于 ready。直接调用时可另存 HTTP 状态及响应头;脚本只检查取到的响应体与 curl 成败,不输出服务器完整日志。

正常路径的可复跑判据是:记录 JDK/Maven/服务器版本及 WAR 摘要,构建退出 0、WAR 有资源类、部署没有失败、脚本退出 0、响应体为 ready。这五个观察量分别来自构建工具、包文件、服务器日志和 HTTP 客户端,缺一个就不能把上一层成功直接推给下一层。健康检查可以作为接入层的烟测,不能用于判断“所有依赖均可用”;若进程尚未部署完就发请求,还要记录失败发生的时间与部署完成时刻,不能把启动中的 404 写成资源方法坏了。

失败路径不用虚构容器异常:在隔离运行中把 JAVAEE_PORT 改为一个确定没有监听的本地端口后运行脚本,预期 curl 非零退出且无法取得 ready;恢复原端口后再运行作对照。若希望验证路径配置,可在不修改源码的前提下请求 /procurement/api/missing,记录 HTTP 状态、时间及日志;预期是未匹配资源,不要把具体错误页面文本当作规范保证。若两次都成功,先检查端口上是否有别的服务、是否把测试请求发给了预期 WAR。停止服务器、错误路径和数据库配置错误分别影响不同层,不能用一次失败代替全部失败验收。

已有一次有界的正常路径实测。writing-plans/javaee-enterprise/verification/20261004T061900Z-pg16-foundation/ 中的 build-3.stdout.txt 显示 clean verify 的五个 Maven reactor 项均 SUCCESS,领域模块运行一个测试方法,零失败、零错误,并打出 procurement-webapp-1.0-SNAPSHOT.war。server-final.stdout.txt 记录 Open Liberty 26.0.0.5 在 Java 21.0.12.1 上启动、应用挂载到 http://localhost:9085/procurement/,启用 jakartaee-11.0、restfulWS-4.0 等特性。health.headers.txt 有 HTTP/1.1 200 OK,health.body.txt 是 ready,health-exit-code.txt 为 0。因此“这次 WAR 构建且最小容器路由可响应”有原始依据;服务器特性列表与 HTTP 200 均不能证明采购事务、库连接、身份授权或通知账本完成。路径与文件索引见本章研究卡与复跑记录模板;工作树源码与该记录的基线提交关系须在归档时另行核对,不能由目录名推断完全相同的构建快照。

尚未找到错误端口、错误资源路径及恢复后的原始请求证据,所以本章负向实验仍为 NOT_RUN,不是 FAIL。上面构建日志已经包含一个通过的领域测试,但它的断言范围是纯 Java 金额与状态转换,不能把此结果计作 REST 业务接口或数据库用例通过。可复跑命令保留在正文,后续如需比较重新构建结果,记录新一次的代码提交、脱敏配置、时间和响应,不要拿旧的 200 当作新部署的结果。

在不查库的章节仍需辨认数据库配置,因为 server.xml 已定义 DataSource 的库名、驱动库路径和环境变量。部署若因为资源配置失败,要把问题定位在服务器装配阶段,不能断言健康方法的 Java 代码有错;部署若成功,也不能推断驱动真的做过一次成功的连接。实验记录应分列“启动时的资源配置”与“业务使用时的资源验证”,未触发后一列就明确空缺。尤其不要把 JPA 先行工程使用的 jpa_lab 当成此处的 javaee_lab:错连库即使偶尔读到数据,也无法证明当前采购工程的结果。

故障注入优先选择不破坏数据库的端口和路径。测试错误端口时,预期失败点在客户端建立连接之前;测试未注册资源时,预期请求至少到达正确的 Web 上下文。两种错误的诊断路径不同。若脚本失败后同时改了服务器、数据库和源码,恢复成功仍无法确认哪一项解决了故障。一次只改一个输入,保留失败命令、退出码和原值,再恢复并重放正常请求,才能得到可定位的失败;如果没有这些原始材料,结果应继续保持 NOT_RUN。

两道练习与答案

练习一:客户端访问 /health 获得 404,服务器日志却显示 WAR 部署成功。先从实际源码和配置推导正确路径,再说出一项部署成功仍不能证明的事实。不要通过改健康资源的返回值解决 URL 问题。

答案:上下文根 /procurement、应用前缀 /api、资源路径 /health 连接后是 /procurement/api/health。部署成功只证明服务器接受部署包;没有在该 URL 请求且核对 ready,连路由生效都未证实,更不能推出采购数据已落库。

练习二:领域测试对 DRAFT → APPROVED 抛异常,健康接口同时返回 ready。这个组合是矛盾吗?如果把数据库地址设错,哪一条观察必须重新做,才有可能判断数据库能否供业务使用?

答案:不矛盾。领域测试验证非法状态迁移被拒绝,健康方法只是返回常量。改错数据库地址后健康响应仍可能成功;要证明连接或事务,必须在有数据库操作的独立用例中检查实际连接、提交结果及数据库最终行,并保留该用例的失败与恢复证据,不能重跑健康请求取代。

版本化资料与边界

Jakarta EE Platform 11.0 界定平台与 Java SE 要求;Jakarta EE Web Profile 11.0 和 Core Profile 11.0 用于核对功能范围。REST 注解以 Jakarta RESTful Web Services 4.0 为规范入口;Java SE 21 javax.sql 解释 JDBC 包名的例外。运行时需进一步核对 Open Liberty 26.0.0.5 文档 与所用发行版的官方兼容清单;动态文档入口不替代 26.0.0.5 的实际部署记录。源码是当前仓库中的本地文件,GitLab master 链接只有相应文件推送到目标分支后才可远程打开,不能把尚未发布的链接当作线上验收证据。