深入 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...
深入 Spring 10:作用域、对象身份与销毁责任
prototype 为什么仍被重复使用 订单服务是 singleton,订单计算票据 Ticket 是 prototype。服务构造时收到一个 Ticket,之后每次处理订单都读取同一个字段。即使 Ticket 的声明写着 prototype,第二次业务调用也没有再向容器请求对象。对象仍被服务字段持有,作用域声明不会改写普通 Java 字段访问。 问题可以缩成三个动作:何时向容器请求、请求后保留哪个引用、何时结束资源所有权。singleton 与 prototype 主要改变创建与复用规则;Provider 将请求推迟到调用时;scoped proxy 在每次被拦截的方法调用中解析当前作用域目标。几种机制叠加时,必须分别观察代理和目标的身份。 本文固定 Spring Framework 6.2.11、JDK 21,源码为 4c134254642d88e058aa004bdaf44168e1be7bb2。实验在 examples/spring-framework-lab/src/main/java/blog/spring/Chapter10.java,新增的 Spring Web ...
深入 Spring 09:循环依赖与早期代理一致性
能取得一个引用,不等于对象已经初始化 订单服务 A 需要库存协调器 B,B 又保存 A 的引用。构造器注入和 setter 注入表达的是同一个依赖环,却可能得到不同启动结果。差别在于请求回到 A 时,A 的构造器是否已经返回,以及容器能否提供一个与最终公开引用一致的早期对象。 本文固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。实验位于 examples/spring-framework-lab/src/main/java/blog/spring/Chapter09.java,使用单线程原生 GenericApplicationContext。它覆盖构造器环、setter 环、自动代理环、关闭循环许可四组,并额外构造只在初始化后包装的错误扩展。 A 实现 Order 接口,ready 初始为 false,afterPropertiesSet 才把它改成 true。B.setA 保存引用时立即读取 ready。这个读取使实验能区分“引用注入成功”与“被注入对象已完成初始化...
深入 Spring 08:扩展顺序与过早创建
修改定义与包装实例需要不同的时机 订单服务的默认配置需要在创建前调整,业务方法又需要在创建后增加代理拦截。这两个需求虽然都叫扩展容器,却作用于不同对象:BeanFactoryPostProcessor 修改 BeanDefinition 等工厂元数据,BeanPostProcessor 接收已经创建出来的实例。把前者写成 getBean 后修改对象,会提前触发创建,使后者可能失去机会。 本文固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。完整可运行代码在 examples/spring-framework-lab/src/main/java/blog/spring/Chapter08.java。实验有两个独立上下文:正常场景按阶段修改定义和包装实例;反例在工厂后处理期间提前获取业务 singleton,观察缺失的代理和未生效的属性覆盖。 refresh 中的两个入口不能交换 AbstractApplicationContext.refresh() 在工厂准备完成后调用...
深入 Spring 07:生命周期回调与最终代理引用
初始化回调中的 this 属于目标对象 订单服务在初始化方法里调用自己的业务方法,容器启动后外部请求又调用同一个方法。如果业务方法由代理拦截,两次调用不一定经过同一条路径。初始化回调作用于正在创建的目标对象,最终提供给调用方的引用可以在后处理阶段被替换为代理;方法名相同不意味着入口相同。 本文固定 Spring Framework 6.2.11、JDK 21,源码提交为 4c134254642d88e058aa004bdaf44168e1be7bb2。实验使用普通 GenericApplicationContext,显式注册后处理器,避免组件扫描带入无关扩展。标准生命周期注解来自 Jakarta Annotation 2.1.1。这里使用 Java 21 编译,代码中的 var 和 List.of 不能直接用于 Java 8。 完整程序是仓库 examples/spring-framework-lab/src/main/java/blog/spring/Chapter07.java。实验同时保存回调顺序、初始化时的 this、容器公开引用和拦截次数,单靠“某条日志出现了”无法证明...
深入 Spring 06:构造器、工厂与延迟创建
查询名称相同,得到的对象却不同 注册一个名为 product 的 FactoryBean<Product> 后,getBean("product") 返回产品,getBean("&product") 返回工厂。工厂可能已经存在,产品却尚未创建;产品被取得一次之后,后续查询又可能直接命中产品缓存。只在 Product 构造器打断点,会漏掉工厂实例的生命周期。 订单应用可能通过构造器创建普通服务,通过静态方法适配第三方对象,通过实例工厂生成业务组件,再用 lazy 或 provider 控制取得时点。这些方式分别改变“如何创建”和“何时请求创建”,两组选择可以组合。工厂方法本身也可以带依赖参数,仍需使用第 05 篇讨论的依赖解析。 本文固定 Spring Framework 6.2.11、JDK 21,源码提交为 4c134254642d88e058aa004bdaf44168e1be7bb2。完整实验在仓库 examples/spring-framework-lab/src/main/java/blog/spring/Ch...
深入 Spring 05:依赖解析如何筛选候选对象
同一个接口出现两个实现以后 订单应用按渠道选择处理策略。Channel 有 Web、Store 两个实现,消费方声明一个 Channel 构造参数。只有 Web 时依赖可以确定;同时注册两个实现以后,容器需要额外的选择条件。把参数改成 List<Channel>,需求则变成取得所有匹配策略,不再要求从中选一个。 这一差别决定了 @Qualifier、@Primary 和集合注入的作用范围。Qualifier 缩小可用候选集,Primary 帮助单值依赖确定优先对象,集合表达多值需求。三者不能用一张“注解优先级”表解释。官方 Qualifier 文档也允许多个 Bean 具有同一限定值,并一起进入集合。 本文固定 Spring Framework 6.2.11、JDK 21,使用原生 AnnotationConfigApplicationContext。源码对应完整提交 4c134254642d88e058aa004bdaf44168e1be7bb2。完整实验位于仓库 examples/spring-framework-lab/src/main/java/blog/sp...
深入 Spring 04:配置类解析与 Bean 方法调用
同一个 repository 方法,为何得到不同对象 @Bean OrderService service() { return new OrderService(repository(), new PricePolicy()); } 看起来只是普通 Java 工厂方法。若它位于默认的 @Configuration 中,repository() 可以返回容器管理的仓储;把配置改成 proxyBeanMethods=false,同一个调用在本例中却会执行 new OrderRepository(),得到没有交给容器管理的另一个仓储。 差异不在 OrderRepository 是否加注解,而在配置类实例的来源和方法调用路径。本篇检验扫描、@Import 与 @Bean 如何形成定义,以及 full/lite 两种处理模式怎样改变跨 Bean 方法调用。基线为 Spring Framework 6.2.11、JDK 21,源码固定到 4c134254642d88e058aa004bdaf44168e1be7bb2。 图据 ConfigurationClassParser、Config...
深入 Spring 03:refresh 如何组织容器启动
启动失败发生在哪一步,会改变清理结果 “Spring 启动时创建所有 Bean,失败就销毁全部对象”不足以解释实际故障。有些对象是后处理器,业务对象创建前就必须存在;有些业务对象设置为懒加载,启动成功时仍未创建;某些前置准备失败甚至没有进入统一销毁的异常捕获范围。 本篇检验的问题是:refresh() 中的阶段顺序怎样限制扩展点,异常落点又怎样影响已创建对象?实验采用 Spring Framework 6.2.11、JDK 21 的 GenericApplicationContext,不借用 Boot 启动日志推断原生 Framework 行为。实现引用固定到 4c134254642d88e058aa004bdaf44168e1be7bb2。 图据 AbstractApplicationContext.refresh 绘制。内层异常分支覆盖图中第二组初始化阶段;外层 finally 负责恢复启动线程标记并释放锁,它不等同于销毁 Bean。 refresh 是阶段协议,不是对象创建循环 容器先执行 prepareRefresh,记录启动状态并准备环境;随后 obtainFresh...
