深入 Spring 02:BeanDefinition 如何描述对象创建
注册一条定义,是否已经创建一个对象 一个订单服务依赖仓储和计价规则。手动装配时,构造器调用同时决定创建时点与依赖对象;改成 Spring 后,配置可以先记录“服务由哪个类、哪些依赖创建”,等配置处理完成再创建实例。BeanDefinition 承担的正是这段尚未执行的创建描述。 本篇检验一个问题:同一订单业务改用编程注册、XML 和配置类后,注册、实例化与名称解析是否仍是三件不同的事?实验固定 Spring Framework 6.2.11、JDK 21;实现依据固定到 4c134254642d88e058aa004bdaf44168e1be7bb2。同系列第 01 篇讨论谁拥有生命周期,本篇进一步区分对象的描述与对象本身。 图据固定版本的 DefaultListableBeanFactory、BeanDefinitionReaderUtils 和 ConfigurationClassBeanDefinitionReader 绘制。图中的单例缓存只表示 singleton 路径,prototype 不保留同样的共享实例。 定义保存创建条件,而非对象字段的快照 BeanDefin...
深入 Spring(01):IoC、对象图与容器身份
同一个类注册进 Spring 后,程序还能通过 new 创建它。两次 getBean() 也未必得到同一个对象。决定对象身份的是容器中注册的定义及其 scope,而不是类名上出现过哪个注解。 本篇的问题是:定义、实例和依赖引用分别存在于哪里,怎样证明一个服务持有的对象就是预期的容器对象?实验沿用第 00 篇工程,固定 Framework 6.2.11、JDK 21,通过 == 和构造次数检查身份。它讨论普通 Bean,FactoryBean 产品和代理替换将在后文单独展开。 从类关系到实例引用 OrderService 的构造器声明一个 OrderRepository 和一个 PricePolicy 参数,表达的是类层面的协作要求。一次具体构造才建立实例关系:service.repository 指向哪一个仓储取决于实际传入值。同类仓储可以同时存在,类图无法表达它们属于哪个上下文、已保存哪些订单。 容器中的对象图至少涉及三个层次:定义记录创建信息,实例承载状态,实例字段保存依赖引用。BeanDefinition 存在时可以没有对应实例;实例创建后也不会变成定义。把两者混在一起,会...
深入 Spring(00):从手动组装到可验证的容器实验
订单服务能够计算金额并保存订单,是否必须使用 Spring?可以先直接创建三个对象,调用结果与容器装配后的结果相同。差异出现在依赖由谁选择、对象在何时创建、失败在何处暴露,以及关闭时由谁执行清理。理解这些差异,比记住一个启动注解更适合作为源码阅读的起点。 本篇固定一个可检验的问题:把同一份订单业务交给容器装配,业务结果、缺依赖时的异常与资源终态分别怎样变化?示例使用内存仓储,仅运行单线程调用。实验不涉及 HTTP、事务、数据库或 Spring Boot。 冻结版本与复现入口 教学工程采用 Spring Framework 6.2.11、JDK 21,对应 release tag v6.2.11,完整源码 SHA 为 4c134254642d88e058aa004bdaf44168e1be7bb2。官方发布记录与版本提交用于核对坐标和源码。 Framework 6.x 最低要求 Java 17,采用 Jakarta 命名空间。本系列运行 JDK 21;操作系统默认的 Java 8 无法编译本实验。6.2 已于 2026 年 6 月结束开源支持,因此这里固定它作为机制研究版本。生产版...
Scala E08:Capture Checking 与能力逃逸
一个函数在资源有效期内创建闭包,再把闭包返回到外部,闭包就可能在资源关闭后继续使用该资源。普通返回类型 () => Int 只描述参数与结果,没有显示函数保存了哪些能力。Capture checking 研究把这部分引用关系放入类型检查,使某些能力逃逸在编译时被拒绝。 这是一项实验性语言能力。本篇固定 Scala 3.7.4、-experimental 与 language.experimental.captureChecking,只研究这一组合下的通过与拒绝样例,不把它作为主线稳定语义,也不据此建议生产系统立即采用。后续文档与实现可能继续变化。 独立工程位于 examples/scala-lab/electives/E08,成功记录为 evidence/20261002-b/。样例使用一个逻辑能力对象,不打开真实文件;它验证捕获约束,不假装已经完成文件资源管理或完整效果系统的安全证明。 返回值里可能隐藏引用 假设 File 对象提供 write 方法,使用函数把它交给调用方回调。普通 Scala 代码允许回调返回任意结果,因此也可能返回一个捕获 File 的函数。 123...
Scala E07:类型状态 DSL 与两种解释器
订单确认之后不允许继续添加商品,可以在运行时检查一个布尔字段,也可以让确认前后的对象具有不同类型。类型状态 DSL 把部分调用顺序约束交给编译器:草稿允许添加,确认结果允许提交,非法链条在运行之前就被拒绝。 这个能力有明确边界。编译器能检查当前引用暴露哪些操作,却不会替数据库判断订单是否已被另一台机器提交,也不会让一个普通 Scala 引用只能使用一次。正确的 DSL 需要同时说明它排除了什么,以及哪些业务事实仍然必须动态检查。 本篇冻结 Scala 3.3.7 与 JDK 21,不依赖第三方 DSL 框架。独立工程位于 examples/scala-lab/electives/E07,通过证据为 evidence/20261002-a/。工程同时运行纯解释器和带写入回调的执行解释器,并将非法操作放在单独编译单元中验证。 状态进入类型参数 订单有 Draft 与 Confirmed 两种状态。状态类型只参与编译检查,不需要为每个对象保存一个运行时布尔标记。订单内部保存命令序列,构造器限制在 DSL 定义内部,调用方通过入口方法获得草稿。 12345678910111213seal...
Scala E06:Spark 的版本、闭包与重执行
把普通 Scala 集合的 map 改成 Spark RDD 的 map,函数看起来相似,执行责任却发生了变化。函数可能被序列化、送到执行端,在 action 触发时运行,还可能因再次计算或故障恢复而重复执行。捕获一个驱动端对象、修改一个外部变量、发送一次业务请求,都需要重新检查含义。 本篇使用真实 Spark 4.0.0 本地模式,运行两个有边界的实验:对同一条未缓存 RDD 执行两次 action,观察转换被重新执行;在闭包中捕获不可序列化对象,观察任务提交被拒绝。本地实验不模拟多机网络分区、执行器丢失或真实集群容错。 工程位于 examples/scala-lab/electives/E06。冻结 Spark 4.0.0、Scala 2.13.16、JDK 21,成功记录为 evidence/20261002-c/spark-local.log。主线 Scala 3.3.7 不参与这个独立工程的编译。 版本矩阵先于业务代码 Spark 4.0.0 官方文档要求 Java 17 或 21,并使用 Scala 2.13。Maven 发布的 spark-core_2.13:4.0...
Scala E05:Native 与 C 接口的内存责任
Scala Native 可以把 Scala 程序编译为本机可执行文件,并通过 FFI 调用 C 接口。程序仍使用 Scala 类型和集合,但一旦拿到 C 指针,内存所有权就不能沿用“对象不再引用时由 JVM GC 回收”的假设。分配器、释放器、指针有效期与 ABI 都成为接口契约。 本篇把订单纯计算与一个小型 C 资源接口放在同一程序里。纯核心返回 3000 分,C 函数为这个整数分配内存,Scala 读取后通过配对函数释放,再查询 C 侧活动分配计数。这样能够同时验证目标编译、符号链接、指针访问与释放路径。 实验冻结 Scala Native 0.5.8、Scala 3.3.6,使用本机 Apple clang 21.0.0、aarch64 macOS;构建工具运行于 JDK 21。工程在 examples/scala-lab/electives/E05,最终通过记录为 evidence/20261002-e/native-ffi.log,绑定实际引用的 E04/Core.scala 摘要。该次编译设置 JAVA_TOOL_OPTIONS=-Xmx2g,限制编译 JVM 的最...
Scala E04:共享核心与 JavaScript 平台边界
一段订单金额计算可以同时运行在服务端 JVM 和前端 JavaScript 环境。复用它能够减少规则分叉,但相同的 Scala 源码并不意味着可以随意调用相同的 Java 类库,也不意味着两个运行环境的所有细节都一致。跨平台边界需要从编译、链接和实际运行三个阶段分别验证。 Scala.js 将 Scala 程序编译并链接为 JavaScript。它提供 Scala 标准库以及部分 Java API 的实现,使常见集合与纯业务逻辑能够复用。文件系统、线程、反射等能力则与目标平台有关,不能把 JVM 上的可用性当成 JavaScript 环境的承诺。 本篇冻结 Scala 3.3.7、Scala.js 1.20.1、Scala CLI 1.9.1;链接工具在 JDK 21 上运行,产物由本机 Node.js 22.22.2 执行。独立实验位于 examples/scala-lab/electives/E04,通过记录为 evidence/20261002-c/。这里没有运行浏览器 DOM、打包网站或访问真实服务。 先确定共享代码的职责 共享核心只接收订单行,返回金额或校验失败,不读取...
Scala E03:ZIO 的环境、错误与任务生命周期
数量超限、程序空指针、用户主动取消,都可能让订单处理没有返回成功值。把它们统一记成“调用失败”,上层就很难决定该提示用户修正数量、报警修复程序,还是安静结束已经取消的工作。ZIO 把预期错误放在类型参数里,同时保留能够描述缺陷和中断的终止原因。 ZIO[R, E, A] 描述一项计算:执行需要环境 R,业务失败值属于 E,成功值属于 A。这个签名让一部分依赖和失败能够进入类型检查,但没有承诺“除了 E 什么也不会发生”。缺陷、运行时中断以及进程终止仍然需要单独理解。 本篇冻结 Scala 3.3.7、ZIO 2.1.16、JDK 21。独立实验目录为 examples/scala-lab/electives/E03,成功记录是 evidence/20261002-b/error-channels.log。实验只验证本机 fiber 的生命周期,不比较不同效果库的整体优劣。 环境参数表达显式依赖 订单数量校验依赖一个上限。把上限直接写成全局变量,调用方无法从方法签名看出依赖;把它放进 Config 环境后,校验描述可以先构造,再由调用端提供具体配置。 1234567final ca...
Scala E02:FS2 的需求、背压与资源作用域
从一百万条订单里取前五条,不应先把一百万条读入集合。可是把集合改成 Iterator,仍然没有说明谁负责关闭文件,也没有说明把处理交给异步任务后,上游可以领先多少。流式处理至少要回答三个问题:何时请求下一份数据,允许保留多少未消费数据,以及消费提前结束时谁关闭资源。 FS2 将这些问题放进带效果的流描述。一个 Stream[IO, A] 可以分批产生 A,在生产过程中执行 IO,并把资源作用域与消费过程连接起来。它不是预先装满的容器;compile 把流整理为目标效果,随后效果运行时才进行本次消费。 本篇使用 Scala 3.3.7、FS2 3.11.0、Cats Effect 3.5.7 与 JDK 21。实验位于 examples/scala-lab/electives/E02,只处理本机临时文件,没有远端服务、消息队列或吞吐量基准。最终通过记录是 evidence/20261002-d/file-backpressure.log。 从需求端解释读取次数 输入文件包含 1 到 100,每行一个整数。生产端每次执行一次 readLine,把成功读到的行计入 Ref。消费端先等待...
