Lombok 拾遗
Created|Updated|Java
|Word Count:43|Reading Time:1mins|Post Views:
sneakyThrow
1 | |
Author: magicliang
Copyright Notice: All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Related Articles
2026-10-04
Java常用类库-E08-MapStruct与Lombok的生成代码契约
删除手写代码之前先保留行为契约 商品输入对象需要转换成展示对象,还需要支持更新已有对象。MapStruct 可以生成字段映射,Lombok 可以生成访问器、builder 和相等方法。代码行数减少之后,null、集合引用和 hashCode 行为仍然存在;如果不检查生成结果,就可能把原本看得见的决策变成构建过程中隐含的默认值。 本篇使用独立 Maven 模块,实际运行 MapStruct 1.6.3 与 Lombok 1.18.42,并显式加入 lombok-mapstruct-binding 0.2.0。固定源码分别为 b4e25e49deae707b50ce061172e114292b414a23 与 2031eb0880942b5f0b7281580f6e877a3e87279a。所有应用源码保持 Java 8,在 JDK 8 和 JDK 21 分别 clean verify,没有用已经生成的 class 代替处理器执行。 完整项目见 下载处理器实验工程,测试见 ProcessorContractTest.java,生成映射见 ProductMapperImpl.java,...
2026-10-04
深入 Play 32:版本迁移,把编译通过和协议兼容分开验证
把 Play 2.9 应用中的 akka.util.ByteString 留到 Play 3,编译会失败。把 play.server.akka.server-header 留到 Play 3,应用却能启动,配置查询也能返回旧值,实际 HTTP 响应仍然没有那个 Server 头。两处改动都涉及 Akka 到 Pekko 的命名迁移,故障出现的位置并不相同。 版本迁移需要同时检查 Java 类型、解析出的依赖、配置的实际消费者和网络行为。compile 能发现缺失的类,无法证明某个字符串配置已被服务器使用;单元测试能检查 Result,也无法替代打包进程在线路上发送的完整响应。 四个组合,分别改变框架和 Scala 实验使用四个独立的 Play Java 工程。前三个固定 Scala 2.13.15,依次迁移 Play 2.8.22、2.9.6、3.0.6;第四个保持 Play 3.0.6,把 Scala 改为 3.3.4。所有组合使用 sbt 1.10.7 和 Amazon Corretto 21.0.11,同一套 Java 路由和 Python HTTP 断言贯穿整个矩阵。 ...

2026-10-04
设计模式 E08:遗留代码的小步重构
没有测试的遗留代码,第一步不是套模式,而是把现有行为钉住。特征测试不代表旧行为都正确,它只说明这些输出不能在重构时无声改变。接缝建立以后,再把兼容行为、已知缺陷和确认后的变化分开。 特征测试先冻结可观察行为 labs/E08 的 LegacyCheckout.checkout 解析 sku:cents:state。开放订单返回九折收据,被封禁返回 DENIED,解析失败返回 ERROR。特征测试覆盖成功、封禁、未知状态、负金额、最大 int 金额、空 SKU、字段缺失、金额解析失败和溢出解析失败。测试还记录一个已知缺陷:空 SKU :100:open 返回 OK::90。记录它的目的,是防止重构时顺手改变外部可见行为。 123456assertEquals("OK:paper:90", legacy.checkout("paper:100:open"));assertEquals("DENIED", legacy.checkout("paper:100:blocked"));assertEquals(...

2026-10-04
设计模式 01:订单身份和金额值为什么不能混为一谈
把金额 10.00 CNY 作为报价索引的键,保存后又在同一个对象上改成 11.00 CNY。此时“持有同一个对象引用”并不能保证还能从 HashMap 找回报价:Before.MutableAmount 的 hashCode 取决于可改的金额。实验先断言存入后能查到,再改金额,最后断言当前输入和 JDK 下查询结果是 null。这不是 HashMap 必然在所有键修改后都查不到的定律,而是说明键入表后的散列语义不再稳定。 第 00 篇守住了旧报价 50.00 × 2 = 100.00,并把会员新折扣另列断言。这里不改变报价契约:Before.total、After.total 和不建金额类的 Alternative.total 对同一 OrderLine 运行同一组参数化断言,三次均得到 100.00。新需求是金额要作为稳定的值传给多个协作者,同时订单改价不能改变订单的身份。 值相等不是引用相同 教学工程在 order-core 新增了 Money:一个 record,包含十进制金额和币种代码。构造时以 RoundingMode.UNNECESSARY 将金额归一到两位小数:1...

2026-09-06
从零构建现代搜索引擎(08):短语、布尔查询与过滤
上一篇的倒排索引只支持单词查询和最基本的 AND/OR。搜索 “Java 内存模型” 时,AND 查询能找到同时包含 “java”、“内存”、“模型” 三个词的文档,但也会命中一篇标题是"Java 并发编程"、正文某处提到"内存不足"、另一处提到"设计模型"的文档——三个词散布在不同段落,根本不是"Java 内存模型"这个概念。 短语查询(phrase query)要求词项不仅共现,还必须按指定顺序出现在相邻位置。这需要用到第 06 篇记录的 position 信息。 本篇在倒排索引上增加三项能力:短语查询、带优先级的布尔查询语法、以及字段过滤。 短语查询:Positional Intersection 核心思路 短语 “Java 内存模型” 经过分析器产出三个词项,position 分别为 0、1、2。在目标文档中,这三个词项也必须出现在连续的 position 上(允许 position 差恰好等于词项在查询中的间距)。 算法分两步: 对短语中所有词项的倒排列表做 AND(交集),找到同时包含...
2026-10-04
深入 Play 02:一次请求在哪个阶段失败
同一个订单接口可能返回 400、413、415 或 500。只看到状态码,无法知道业务方法是否执行:400 可能来自路由参数绑定,也可能来自 JSON 解析;500 可能来自同步抛异常,也可能来自异步结果失败。排查需要把输入、阶段事件和客户端观察对应起来。 当前工程在过滤器入口、控制器入口以及结果 Stage 的成功或失败处分别打点。它没有给每个源码函数加日志,而是用最少的事件回答一个问题:请求已经越过哪些边界,在哪个分支停止? 先找到 handler,再包装过滤器 Play 3.0.6 的默认请求处理器先选择 handler,再为可过滤的 action 包装过滤器。这一点影响读源码的方式:过滤器的执行入口早于控制器,不意味着 Router 对 handler 的选择也发生在过滤器之后。 固定的 DefaultHttpRequestHandler.handlerForRequest 依次调用 routeWithFallback、Handler.applyStages、filterHandler,再处理一次预处理阶段。filterHandler 为应用上下文内的 Essentia...
Contents
