深入 Spring 16:切点的静态筛选与运行时匹配
实参是 String,为什么 execution 不匹配 订单接口有一个 Object submit(Object input) 方法。调用者传入字符串 "order",args(java.lang.String) 的通知执行了,execution(* submit(java.lang.String)) 的通知没有执行。两条表达式观察的对象不同:前者约束这次调用的实参类型,后者约束方法声明的签名。 这个差异使切点成为可以验证的筛选算法。只盯着表达式的英文含义,会把声明类型、运行时类型、目标类型和代理类型混为一谈。本实验把方法固定为 submit(Object),分别改变表达式、实参和代理种类,观察通知计数。 环境为 Spring Framework 6.2.11、AspectJ Weaver 1.9.22.1、JDK 21。这里使用 AspectJ 的表达式解析器,没有启动字节码 weaving。Spring 源码 SHA 为 4c134254642d88e058aa004bdaf44168e1be7bb2,完整入口为 examples/spring-fram...
深入 Spring 15:Advisor 怎样进入 Bean 的创建过程
同一个订单服务为何取得的是代理 new OrderTarget() 返回业务对象,容器 getBean("orders") 却可能返回代理。Advisor 没有修改 Java 的 new 指令;自动代理创建器作为 BeanPostProcessor 参与实例暴露,决定交给调用者的引用。 本实验先手动 ProxyFactory 包装一次,再把相同 Advisor 交给 DefaultAdvisorAutoProxyCreator,让容器创建订单与观察者两个 Bean。观察者回指订单,订单又持有观察者,强制出现早期引用需求。可证伪的问题是:观察者注入的早期对象是否与 refresh 后查到的最终对象相同?若只是数通知执行了几次,无法回答对象图是否一致。 环境固定 Spring Framework 6.2.11、JDK 21,源码 SHA 为 4c134254642d88e058aa004bdaf44168e1be7bb2。可运行代码为 examples/spring-framework-lab/src/main/java/blog/spring/Chapter15...
深入 Spring 14:JDK 与 CGLIB 代理的拦截边界
final 方法为何在两种代理中得到不同结果 订单接口声明了 fixed(),实现类把它声明成 final。同样一个计数通知,通过 JDK 代理调用时计数增加,通过 CGLIB 代理调用时计数不增加。不能据此判断 Spring 偶尔忽略了 final,也不能把“final 不能被代理”当成不带条件的规则。 JDK 代理实现接口,接住接口调用以后委托目标对象;它不需要覆盖实现类的 final 方法。类代理依赖子类覆盖可以覆盖的方法,把调用转入拦截器。final 限制覆盖,private 方法不被子类继承,这两种限制与选用的代理入口直接相关。JLS 21 方法覆盖与 final 本文要验证的问题是:改变代理策略后,哪些调用确实经过通知,哪些只在类型上看似相同?实验固定 Spring Framework 6.2.11、JDK 21,源码提交 4c134254642d88e058aa004bdaf44168e1be7bb2。完整入口为 examples/spring-framework-lab/src/main/java/blog/spring/Chapter14.java。 把接口、...
深入 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...
深入 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、容器公开引用和拦截次数,单靠“某条日志出现了”无法证明...





