深入 Spring 42:AOT 生成代码与运行时配置的边界
启动参数出现 red,容器里的 Bean 仍然是 blue 配置类定义两个同类型 Bean:@Profile("blue") 返回 blue,@Profile("red") 返回 red。普通 JVM 分别带 blue、red 启动,容器里也分别出现对应 Bean。用 blue 生成 Spring AOT 产物后,再给 AOT JVM 传 red,实验仍取得 blue Bean。 日志中有效 profile 是 blue,red,这并不矛盾。环境属性仍可在启动时参与处理,已经生成的 Bean 注册代码却不会重新执行一次任意的配置类发现与条件选择。把“Environment 中出现了某个值”当作“容器根据该值重建了 Bean 集合”,会误判 AOT 应用的装配行为。 实验固定 Framework 6.2.11、Boot 3.5.6 与 JDK 21。final-lab/AotApp 是独立的非 Web 入口,避免把数据库连接和服务器启动引入 AOT 基础实验;它与订单入口共享 Maven 模块和冻结依赖。普通 JVM、生成代码、AOT JVM...
深入 Spring 41:测试边界与提交回调的盲区
订单测试通过,提交后的通知仍然失败 订单方法在事务中插入数据,并注册一个 afterCommit 回调。测试调用该方法,检查插入成功,最后由 Spring Test 自动回滚。整组测试通过,但真实 HTTP 请求一提交就抛异常。这两个结果可以同时成立:测试从未走过提交路径,回调里的错误也从未执行。 测试的价值取决于实际经过的边界。纯函数断言证明金额规则;MockMvc 证明参数绑定和异常到响应的映射;真实服务器加独立数据库连接证明请求线程完成了提交。把三类结果都写成“订单接口测试通过”,会丢掉最需要保留的条件。 实验固定 Spring Framework 6.2.11、Spring Boot 3.5.6、JDK 21 和 PostgreSQL 18.0。Boot BOM 管理本模块依赖,数据库使用独立 spring_final schema。JUnit 实际执行 8 个测试方法,产生 15 条具名断言,没有将手写演示输出当作 JUnit 报告。依赖清单、XML 报告与原始输出随实验工程保留。 从同一个金额错误区分三种证明 业务规则 Store.validate(amount) ...
深入 Spring 24:JDBC、ORM 与多个事务管理器的资源边界
同一个方法里的两条写入,为什么只回滚一条 一个方法先用 JdbcTemplate 插入订单,再从连接池直接取得连接插入审计记录,最后标记回滚。订单消失,审计记录仍在。这不需要跨数据库才能发生:同一连接池提供的两条物理连接,就足以产生两个不同的事务结果。 事务管理器协调具体资源。代码位置相邻、使用相同 JDBC URL、位于同一个 Spring Bean,都不能证明两个操作参加了同一个事务。检查事务时,需要把管理器、资源键、连接取得路径以及数据库会话对应起来。 本文固定 Spring Framework 6.2.11、JDK 21.0.11、PostgreSQL 18.0、pgJDBC 42.7.7、HikariCP 5.0.1。ORM 使用独立工程冻结 Hibernate ORM 6.6.15.Final 和 Jakarta Persistence 3.1.0,避免把 ORM 依赖加入全部基础章节。Hibernate 6.6 的官方兼容矩阵列出 Jakarta Persistence 3.1 和 Java 21;这些版本在本章实际组合运行,不表示使用了最新版本。Hibernate...
深入 Spring 23:提交回调与事务事件的保证边界
数据已经提交,调用为什么还能失败 订单事务提交后发送通知,通知代码抛出异常。调用方得到失败响应,却在下一次查询中发现订单已经存在。这两项结果可以同时成立:异常来自提交后的回调,而订单提交已经结束。把“方法抛异常”直接等同于“数据库回滚”,会让重试重复创建订单。 事务事件提供的是阶段绑定。它能把监听器安排在提交前、提交后或回滚后,却不替外部系统持久保存通知,也不自动重试。要判断一次业务是否成功,至少应分开观察数据库终态、回调是否调用、异步任务是否完成和接收方是否确认。本文实验只涉及前三项,没有外部消息接收方。 实验固定 Spring Framework 6.2.11、JDK 21.0.11、PostgreSQL 18.0、pgJDBC 42.7.7 和 HikariCP 5.0.1。源码固定提交 4c134254642d88e058aa004bdaf44168e1be7bb2;完整程序是 examples/spring-framework-lab/src/main/java/blog/spring/Chapter23.java。数据库结果由单独建立的连接读取,避免把当前事务自己的可...
深入 Spring 22:回滚规则、异常捕获与rollback-only
抛出异常与数据库回滚不是同一个结果 事务方法插入一行后抛出 checked exception,调用方捕获到异常,数据库却保留了那一行。另一个事务方法捕获内部异常并正常返回,调用方反而收到 UnexpectedRollbackException,数据库没有留下记录。两个结果都符合 Spring 的默认行为,它们分别涉及回滚规则与共享事务的 rollback-only 状态。 默认规则把 RuntimeException 和 Error 视为回滚原因,普通 checked exception 不触发回滚。这个规则处理的是穿过事务拦截边界的异常;在方法内部已捕获的异常不会自动传递给拦截器。同时,方法正常返回也不能清除已存在的 rollback-only 标志。默认事务属性源码 本文固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。PostgreSQL 18.0、pgjdbc 42.7.7、HikariCP 5.0.1 提供真实数据库与连接池。每个场景都通过独立连接读取已提交记...
深入 Spring 21:事务传播、保存点与连接池等待
内层成功不等于整个业务成功 外层事务先写订单,内层写审计记录。内层正常返回后,外层抛出异常。审计记录是否保留,取决于内部范围是否拥有独立物理事务:REQUIRED 的写入随外层一起撤销;REQUIRES_NEW 可以已经提交;NESTED 即使成功释放保存点,也仍受外层最终回滚约束。 方法嵌套只描述 Java 调用结构。解释事务传播,还要同时画出逻辑范围、物理事务、数据库连接三条线。一条连接在外层挂起期间仍被借出,内部的新逻辑范围也不必然对应新连接。混淆这两个方向,会同时误判数据原子性和连接池容量。 本文固定 Spring Framework 6.2.11、源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2,使用 JDK 21、PostgreSQL 18.0、pgjdbc 42.7.7、HikariCP 5.0.1。实验通过 TransactionTemplate 直接指定传播,排除自调用是否穿过代理这一变量。第 20 篇声明式入口在解析属性后调用同一个事务管理器;以下结论仍要求那个入口真实生效。 三条时间线如何对应 AbstractPl...
深入 Spring 20:声明式事务的方法属性与代理入口
同一个注解为何产生不同事务 一个订单服务在类上声明只读事务,在写入方法上另选事务管理器。外部调用写入方法时,数据库能够提交;目标对象用 this 调用另一个事务方法时,却可能没有事务。排查这种差异,需要分别回答三个问题:调用是否进入代理、方法最终解析出什么属性、选中的管理器实际控制哪份资源。 注解只是方法元数据。第 19 篇的 JDBC 事务由管理器取得连接、绑定线程、完成提交或回滚;声明式入口增加了一层拦截逻辑,把方法元数据转换成管理器参数。数据库提交并不发生在读取注解时,也不发生在目标方法的最后一行,而发生在代理控制的调用边界上。 本文固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。实验使用 PostgreSQL 18.0、pgjdbc 42.7.7、HikariCP 5.0.1。两个连接池连接同一实验数据库,用于辨认管理器选择;它们不是分布式事务,也不模拟两个数据库的原子提交。 从方法入口到事务属性 TransactionInterceptor.invoke 把...
深入 Spring 13:启停顺序、层级查找与失败后的资源终态
Bean 已经创建,工作线程是否已经可用 订单服务依赖一个后台通知 Worker。Worker 对象构造成功,并不意味着线程池已经启动;close 日志出现,也不意味着任务线程已经结束。对象存在、组件运行、资源终止是三个状态,需要各自的入口和证据。 Spring 的初始化回调处理对象准备,Lifecycle 提供显式 start/stop,SmartLifecycle 再增加自动启动、phase 和带完成回调的停止契约。把业务启动放在哪一层,会改变失败时点和依赖约束。本文用真实 ExecutorService,而不是只打印“启动/停止”的空方法,检查容器启动与关闭对资源的影响。生命周期官方契约 实验固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。完整代码位于 examples/spring-framework-lab/src/main/java/blog/spring/Chapter13.java。每个 Worker 持有独立线程池,start 提交一次任务并限时等待,...
深入 Spring 12:事件发布、监听顺序与完成信号
publishEvent 返回时,通知发送完了吗 订单处理方法发布 OrderPlaced,监听器更新审计记录并发送通知。默认同步监听下,发布调用返回之前,监听器方法已经返回;为广播器配置线程池后,同一行发布代码却可以在监听器仍然等待时返回。是否完成无法仅从方法名判断,必须检查广播器采用的执行方式和业务完成信号。 Spring 的进程内事件机制负责把事件交给匹配的监听器。它没有自动提供持久化、重试队列或远端送达保证。即使监听器方法同步返回,也只证明这段方法的执行边界;如果该方法又提交了后台任务,真正的通知业务仍可能没有完成。ApplicationContext 事件能力 本文固定 Spring Framework 6.2.11、JDK 21,源码提交为 4c134254642d88e058aa004bdaf44168e1be7bb2。完整实验为 examples/spring-framework-lab/src/main/java/blog/spring/Chapter12.java。默认行为通过 AnnotationConfigApplicationContext 与 Even...
深入 Spring 11:属性解析与条件装配的时间边界
属性已经变了,为什么 Bean 仍是旧对象 订单路由在启动时读取地区 east,并据此构造 Pricing。应用运行后,把属性源中的地区改成 west,Environment.getProperty 已返回 west,Pricing.region 却仍为 east。两次观察并不矛盾:一次查询读取当前属性,一次查询读取先前保存的对象状态。 属性解析、定义是否注册、对象是否创建是三个不同动作。Environment 提供 property 与 profile 视图,条件判断在配置处理过程中决定是否保留定义,Bean 工厂再按已经登记的定义创建对象。修改属性源可以影响后续查询,却不会自动重跑所有先前发生的动作。官方 Environment 抽象 本文以原生 AnnotationConfigApplicationContext 为入口,固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。实验文件为 examples/spring-framework-lab/src/main/jav...





