《Java 编程实战》附录 B 依赖选型

把 libraryd 用到的 JDBC 驱动、连接池、迁移工具、HTTP 框架、JSON、校验、日志、指标、追踪、缓存与测试库放在一起对比,说明每个选择的约束、替代方案与不适用场景,并给出一张可复用的选型表、四个常见误判与替换前的检查清单。

选型不是选「最好的库」

同一类能力通常有三到五个成熟实现。它们的差别很少是「能不能用」,更多是:出问题时能不能查到原因、团队里有没有人熟悉、升级路径是否清楚、文档和社区是否还活跃。

这份附录把 libraryd 用到的依赖集中列出来,说明每个选择的约束和替代方案。本书的选择服务于教学,不一定是你的项目的最优解。 读的时候重点看「为什么不选另一个」,而不是记结论。

JDBC 驱动与连接池

PostgreSQL JDBC 驱动

项值
坐标org.postgresql:postgresql
版本42.7.14
作用实现 JDBC 接口,负责协议、类型映射与 SSL

PostgreSQL 只有这一个官方维护的 JDBC 驱动,没有真正的替代品。需要注意的是版本与服务器版本的兼容性:42.x 支持 PostgreSQL 9.4 到 17,JDK 8 及以上。驱动升级通常向后兼容,但涉及 bytea、jsonb、timestamptz 时,建议在集成测试里覆盖。

连接池:为什么是 HikariCP

连接池特点适用
HikariCP轻量、默认配置合理、Spring Boot 默认绝大多数服务端项目
Apache DBCP2老牌、配置项多遗留系统、需要特定行为
Agroal性能好、Quarkus 默认Quarkus 项目
c3p0历史悠久,维护活跃度低不建议新项目使用

HikariCP 的优势不是「最快」这种无法脱离环境验证的结论,而是三点:代码量小、默认值合理、泄漏检测和指标暴露清楚。本书第 3.2 节会讲 maximumPoolSize、connectionTimeout、leakDetectionThreshold 三个最常改的参数。

连接池不是越大越好。数据库的连接数是有限资源,池太大只会把压力推到数据库侧,表现为锁等待和上下文切换。经验起点是「CPU 核数 × 2 + 磁盘数」,然后用真实压测调整;本书给的是测量方法,不是固定值。

数据库迁移:Flyway 与 Liquibase

项FlywayLiquibase
迁移描述SQL 文件XML/YAML/JSON/SQL
学习成本低中
跨数据库抽象弱(SQL 方言自理)强(changeSet 抽象)
回滚社区版有限支持 rollback
适合单一数据库、SQL 可控多数据库、需要审计与回滚

本书选 Flyway,因为读者需要看清真实 SQL,而不是被抽象层遮住。V1__init.sql、V2__book_search_index.sql 这样的命名让 schema 演进一目了然。

Flyway 的坑集中在三处:校验和(修改已执行的迁移会导致启动失败)、并发启动(多个实例同时迁移需要锁)、基线(已有数据库接入时要 baselineOnMigrate)。附录 C 会给出对应报错。

HTTP 框架:为什么是 Javalin

方案模型特点
Javalin轻量、Jetty 之上路由直观、启动快、依赖少
Jetty 原生Servlet / Handler控制细,样板代码多
UndertowHandler性能好,API 偏底层
Netty事件循环适合自定义协议,Web 开发成本高
Spring MVC注解 + 容器生态完整,学习曲线陡
Quarkus / Micronaut编译期装配启动快,框架约定强

本书需要的是一个「能看到路由、中间件、错误处理原始形态」的框架。Javalin 的 API 足够薄:一个 cfg.routes.post(...) 就是一条路由,一个 cfg.routes.before(...) 就是一段中间件,异常映射也是显式注册。读者不会把时间花在框架约定上。

如果你的项目需要认证授权、内容协商、参数解析、OpenAPI 生成等一整套能力,Spring MVC 更省事;如果你需要极致启动速度,Quarkus 或 Micronaut 更合适。Javalin 的定位是「小服务、清晰边界」。

JSON:Jackson

库特点注意
Jackson生态最广、模块多默认配置偏宽松,需显式收紧
GsonAPI 简单对 record、Java Time 支持较弱
JSON-BJakarta 标准实现少,生态不如 Jackson
MoshiKotlin 友好Java 生态使用较少

Jackson 的关键不是「怎么序列化」,而是「默认允许什么」。本书会显式配置:

  • FAIL_ON_UNKNOWN_PROPERTIES:请求出现未知字段时是失败还是忽略,要按契约决定。
  • JavaTimeModule:LocalDate 默认不能直接序列化,必须注册模块。
  • WRITE_DATES_AS_TIMESTAMPS:关闭后输出 ISO-8601 字符串。
  • 循环引用:用 DTO 切断,而不是靠 @JsonIgnore 到处打补丁。

参数校验:Jakarta Validation

方案优点代价
Jakarta Validation + Hibernate Validator标准注解、生态好需要 EL 实现,错误消息要自己组织
手写校验无依赖、逻辑直观规则一多就重复
Spring 的 Validator 接口与 Spring 集成好绑定框架

本书两者都用:字段级规则用注解,跨字段规则(如 dueOn 必须晚于 borrowedOn)手写。只靠注解会把业务规则拆散,只靠手写会重复大量非空、长度、范围判断。

Hibernate Validator 9.x 需要 Jakarta Validation 3.1 与 EL 实现(本书用 Expressly 6.0.0)。缺少 EL 时,消息插值会退化成 {jakarta.validation.constraints.NotBlank.message} 这样的原始模板,这是很常见的配置遗漏。

日志:SLF4J + Logback

组合说明
SLF4J + Logback本书选择;Spring Boot 默认
SLF4J + Log4j2异步日志性能好,配置更复杂
JULJDK 自带,能力有限

SLF4J 是门面,Logback 是实现。应用代码只依赖 slf4j-api,实现由运行时提供,这样测试和部署可以换实现而不改代码。

结构化日志用 logstash-logback-encoder 9.0。它把日志事件编码成 JSON,字段名与 Elastic Common Schema 接近。代价是每行日志更大,磁盘和网络成本上升;第 12.3 节会讲采样和脱敏。

指标与追踪

能力本书选择替代
指标门面Micrometer 1.17.2直接依赖 Prometheus client
指标暴露micrometer-registry-prometheus自建 /metrics 文本输出
追踪OpenTelemetry SDK 1.67.0Micrometer Tracing、Brave
导出opentelemetry-exporter-logging(教学)OTLP exporter + Collector

Micrometer 的价值是「一次埋点,多种后端」:计数器、计时器、Gauge 的 API 不绑定 Prometheus。生产环境通常接 Prometheus + Grafana;本书用日志导出器是为了让读者在本地就能看到 span,不需要额外部署 Collector。

追踪的学习重点是上下文传播:traceparent 头怎么解析、span 怎么嵌套、异步线程里怎么不丢上下文。换导出器不影响这些概念。

缓存

层本书选择替代注意
本地缓存Caffeine 3.3.1Guava Cache、Ehcache容量、TTL、失效顺序
Redis 客户端Jedis 8.0.2Lettuce、Redisson连接模型、线程安全
分布式缓存Redis 7Memcached、Hazelcast一致性、序列化、雪崩

Caffeine 的 API 比 Guava Cache 更现代,支持异步加载和更细的淘汰策略。Jedis 是同步客户端,API 直观,适合教学;Lettuce 基于 Netty,支持异步和响应式,高并发场景更常见。

缓存章节的重点不是 API,而是三个问题:缓存什么、什么时候失效、失效失败怎么办。第 14 章会用具体的书目详情缓存演示。

测试

能力选择说明
测试框架JUnit Jupiter 5.14.4与卷一一致
容器Testcontainers 1.21.4PostgreSQL 模块
HTTP 断言JDK HttpClient + JUnit 断言不额外引入 REST Assured
数据库断言JDBC 直接查询看清真实数据

本书不引入 Mockito、AssertJ、REST Assured。不是它们不好,而是每多一个库,读者就多一层「这是库的行为还是框架的行为」的判断成本。第 15 章的端到端测试用 JDK 自带的 HttpClient,断言用 JUnit 原生 API,足够覆盖契约。

Testcontainers 需要 Docker。CI 环境没有 Docker 时,可以把集成测试放到独立 profile,单元测试仍然可跑;不要用 H2 假装 PostgreSQL,SQL 方言和事务行为差异会让测试失去意义。

构建与镜像

能力选择替代
编译maven-compiler-plugin 3.16.0Gradle、javac 直接调用
单元测试Surefire 3.6.1—
集成测试Failsafe 3.6.1用 Surefire 跑全部
覆盖率JaCoCo 0.8.16Cobertura(已过时)
静态分析Checkstyle 10.26.1、SpotBugs 4.10.5.0PMD、Error Prone
fat jarShade 3.6.2Assembly、Spring Boot 插件
镜像多阶段 Dockerfile、Jib 3.5.2Buildpacks

Shade 的坑是资源合并与签名文件:多个依赖包含 META-INF/services 时需要 ServicesResourceTransformer,签名文件要过滤掉,否则运行时报 Invalid signature file digest。第 16.1 节会给出完整配置。

Jib 不需要 Docker daemon 就能构建镜像,适合 CI;多阶段 Dockerfile 更透明,适合学习和排查。两者不是互斥关系,团队按流水线条件选择。

一张选型表

能力本书主要替代选择理由
JDBC 驱动PostgreSQL JDBC 42.7.14无官方唯一
连接池HikariCP 7.1.0DBCP2、Agroal轻量、默认合理
迁移Flyway 13.11.1LiquibaseSQL 透明
HTTPJavalin 7.2.4Spring MVC、QuarkusAPI 薄,适合教学
JSONJackson 2.22.4Gson、JSON-B生态与模块
校验Hibernate Validator 9.1.5手写标准注解
日志SLF4J + Logback 1.6.6Log4j2默认组合
指标Micrometer 1.17.2Prometheus client门面解耦
追踪OpenTelemetry 1.67.0Micrometer Tracing标准协议
本地缓存Caffeine 3.3.1Guava CacheAPI 现代
RedisJedis 8.0.2Lettuce、Redisson同步、直观
测试JUnit 5 + TestcontainersMockito + H2真实依赖

四个常见误判

  1. 「连接池越大并发越高」。 数据库连接是有限资源,池过大只会增加锁竞争和内存占用。先测再调。
  2. 「用 H2 测过就等于 PostgreSQL 没问题」。 SQL 方言、隔离级别、锁行为、jsonb 类型都可能不同。集成测试必须用真实数据库。
  3. 「日志越详细越好」。 日志有存储、网络和隐私成本。请求体、身份证号、token 不应默认打印。
  4. 「缓存能解决所有性能问题」。 缓存引入一致性问题和失效复杂度。没有测量过的热点,不要先加缓存。

小结

  • 选型看约束:团队熟悉度、排查成本、升级路径,而不是「性能最好」。
  • 本书的组合是教学导向:每一层都能看到原始形态,依赖数量控制在可解释范围内。
  • 连接池、迁移、HTTP、JSON、校验、日志、指标、缓存、测试各有一个主选和一个备选。
  • 最容易被忽略的不是库的 API,而是默认行为:Jackson 的未知字段、Flyway 的校验和、Shade 的签名文件、Hibernate Validator 的 EL。
  • 是否替换某个库,先问「出问题时我能不能查到原因」。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发