从「能运行」到「能上线」
卷一结束的时候,library 已经能做不少事:添加图书、借阅、归还、搜索、计算逾期罚金,有一套 JUnit 测试,也能打成一个可执行 jar。它在你的终端里跑得很好。
然后呢?
你把它交给同事,同事问:接口地址是什么?你把它部署到服务器,运维问:数据存在哪、重启会不会丢?产品经理问:两个人同时借同一本书会怎样?监控系统问:怎么判断它还活着?你自己也想知道:线上返回 500 时,应该先看哪一行日志?
这些问题,卷一一个都没有回答。不是卷一写得不够,而是它们属于另一个阶段的问题。
《Java 编程实战》只做一件事:把 library 从「一个能跑的程序」推进到「一个能部署、能被调用、出问题能查的服务」。这个推进过程有六道门槛,每一道都不是加一个注解就能过去的。
六道门槛
第一道是工程结构。单模块时,所有代码放在一个 src/main/java 下,改起来很方便;代码量一上来,领域规则、数据库访问、HTTP 接口、启动装配混在一起,测试变慢、依赖变乱、改动互相牵连。多模块不是目的,把变化关进边界才是。
第二道是数据持久化。内存 Map 重启即丢,也撑不住并发。接上 PostgreSQL 只是开始:JDBC 怎么用、连接池开多大、SQL 写在哪、索引建在哪、事务边界画在哪,每一个问题都会在流量上来后变成故障。
第三道是对外契约。HTTP 接口不是「把方法暴露出去」这么简单。请求字段怎么校验、错误怎么表达、分页参数怎么约定、未知字段怎么处理,这些都是调用方要依赖的契约。契约一旦发出去,改起来比改代码贵得多。
第四道是并发与异步。Web 服务器天然并发,但并发不等于「开个线程池」。任务在哪个池里跑、队列多长、拒绝后返回什么、异步调用丢失上下文怎么办,这些问题决定服务过载时是降级还是雪崩。
第五道是可观测性。线上不能靠 System.out.println。日志要能按请求串起来,指标要能回答「现在多慢、错多少」,追踪要能定位「慢在哪一段」,健康检查要能让编排系统知道该不该把流量打进来。
第六道是交付。镜像怎么构建、迁移怎么执行、配置怎么注入、发布失败怎么回滚。代码写完只是交付的开始。
这六道门槛,就是本书的六个部分。顺序不是随意排的:没有模块边界,数据库访问会散落各处;没有事务,并发控制无从谈起;没有统一错误契约,可观测性只能看到一堆格式不同的报错。
为什么不用 Spring
这是最常被问到的问题。
Spring Boot 已经把这些基础设施封装得很好:@Transactional 管事务,@Valid 管校验,Actuator 管健康检查,Micrometer 管指标。用它搭一个服务,代码量比本书少得多。
但本书刻意不用。
原因不是 Spring 不好,而是如果一上来就用框架,你会知道怎么配置,却不知道框架在哪个位置替你做了事。事务没生效时,你不知道是代理没织入还是传播行为配错;校验没触发时,你不知道是注解放错位置还是异常被吞掉;指标缺失时,你不知道是端点没暴露还是注册表没装配。
本书先把这些位置手工实现一遍:用 TransactionManager 画出事务边界,用 Validator 触发校验,用一个 /health 路由探测数据库,用 Micrometer 注册计数器和计时器。你会看到它们的原始形态,也会看到没有框架时你必须自己处理的边界情况。
读完这本书再去看 Spring Boot,@Transactional 不再是一个「加在方法上就生效」的魔法,而是一个你已经亲手写过一遍的代理逻辑。附录 D 给了一张概念映射表,把本书的手工实现与 Spring Boot 的对应能力列在一起。
为什么还是 libraryd
市面上讲 Java Web 的书,例子往往换得很勤:这一章用户管理,下一章订单系统,再下一章博客。每个例子都够简单,但它们之间没有关系,读者学完一章,不知道这一章在真实项目里处于什么位置。
本书继续用 library,但让它长大。
- 卷一的
library是一个 CLI,数据在内存里。 - 卷二的
libraryd是一个 HTTP 服务,数据在 PostgreSQL 里,有连接池、事务、缓存、日志、指标和镜像。 - 卷三会把它的并发与语言机制拆开重写。
- 卷四会用 JFR、GC 日志和堆转储观察同一个服务的运行时。
同一个项目跨四卷生长,好处是每个技术点都有落点:连接池不是「HikariCP 有这些参数」,而是「借阅高峰时 10 个连接够不够」;事务不是「ACID 四个字母」,而是「借书时扣减在借数量与写入借阅记录必须一起成功」;缓存不是「加个 @Cacheable」,而是「书目详情缓存 5 分钟后,刚上架的新书为什么查不到」。
坏处是项目会限制示例的自由度。有些库的能力很难自然塞进借阅系统,比如批量导入十万条书目、跨库分页、消息队列。这些内容本书不硬造场景,放到附录或直接说明不在范围内。
这本书不写什么
不写 Spring 生态。 包括 Spring Boot、Spring Data、Spring Security。本站已有 150 篇的《Spring Boot》三部曲,分工见附录 D。
不写 JVM 内部机制。 类加载、JIT、GC 算法、对象布局属于卷三和卷四。本书只在排查问题时提到必要的运行时概念,不展开原理。
不写前端。 libraryd 只提供 JSON API,页面和客户端不在范围内。
不写分布式系统。 本书是单服务、单数据库。分库分表、消息队列、服务网格、分布式事务都不涉及;缓存章节会讲到 Redis,但只讲单实例与一致性取舍,不讲集群运维。
不伪造性能数字。 涉及性能的地方给出可复现命令和测量方法,结论留给读者在自己的机器上验证。不同硬件、不同 PostgreSQL 配置下的数字没有可比性。
验证方式
书中的代码不是「看着对」就写进去的。写作过程中做了四层验证:
- 单文件编译:每个完整类都用
javac 25.0.5编译过。 - 工程构建:每一章结束时的
libraryd状态都能通过mvn -q -DskipTests package。 - 真实依赖:数据库示例在
postgres:17-alpine容器里跑过,HTTP 示例用curl真实请求过,返回码和响应体是实际结果。 - 产物检查:全书用 Hugo 构建,检查章节页、目录页、书架卡片和内部链接;构建失败或链接断裂不会发布。
仍然会有遗漏。同一段 JDBC 代码在 PostgreSQL 16 和 17 上的行为可能略有差异;不同 Docker 版本对端口映射的处理也不完全一样。书中的输出以 macOS + Temurin 25.0.5 + PostgreSQL 17 为准。如果你在 Linux 或 Windows 上看到不同结果,先检查版本、时区和容器网络,再判断是不是书里的问题。
四卷本的路线
| 卷 | 书名 | 回答的问题 | 结束时你能做什么 |
|---|---|---|---|
| 一 | 《Java 编程入门》 | 不会 Java,怎么写出结构清楚的程序? | 独立完成 CLI + 内存存储的项目 |
| 二 | 《Java 编程实战》 | 不用 Spring,怎么把程序做成可交付的服务? | 交付带数据库、缓存、日志、指标、镜像的服务 |
| 三 | 《Java 高级编程》 | 泛型、反射、字节码、并发与虚拟线程怎么工作? | 读懂并改造框架与运行时的关键机制 |
| 四 | 《Java 虚拟机原理与性能调优》 | 代码跑起来后,JVM 里发生了什么? | 用 JFR、GC 日志和堆转储定位性能问题 |
卷二处在中间:它不解释 JVM 为什么这样设计,但会让你在真实问题上遇到这些设计。比如虚拟线程为什么能降低线程成本、连接池为什么不能无限大、GC 停顿为什么会表现为接口超时。这些问题会在卷三、卷四得到完整回答,卷二先让你知道它们存在。
一点期望管理
「实战」不等于「照着抄就能上线」。
书里的 libraryd 是一个教学项目,它的业务规则被刻意简化:会员自动建档、没有权限体系、没有多租户、没有审计日志、没有限流。这些不是遗漏,而是为了让工程问题显形——如果业务足够复杂,你会分不清哪些困难来自业务、哪些来自基础设施。
你真正要带走的是判断力:什么时候该拆模块、连接池开多大、事务边界画在哪、缓存该不该加、指标该看哪几个、发布失败先回滚还是先修。这些判断没有标准答案,但有一套可以练习的方法。本书用 libraryd 给你一套练习材料。
如果你接受这个目标,我们可以从第 1 章开始。那里没有 HTTP,也没有数据库,只有一件很朴素的事:把已经能跑的代码,拆成几个边界清楚的模块。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。