读完这篇,你能做什么
能沿对象生命周期标记资源准备、代理暴露与业务增强真正发生的位置。
学习理由、准备要求与知识点
为什么学它
理解目标对象、初始化和代理时机,可以解释事务与增强在某些调用中为何失效。
先读这些内容
知识点清单
- 实例化与依赖注入
- 初始化回调
BeanPostProcessor- 目标对象与代理
- 切点和通知
- 初始化内部调用限制
- 单例与原型销毁责任
- 正常关闭与共享状态
运行前先确认Java17、Spring Framework6.2;源码固定v6.2.11。流程描述常见非特殊Bean路径,教学资源回滚程序不实现容器的全部销毁和代理语义。
01先弄懂它是什么
构造器执行完,只代表 Java 对象已经构造,不代表 Spring 已经准备好把最终 Bean 交给调用者。容器之后还可能注入属性、执行初始化回调和后处理,最后交出的也可能是包装过的代理。区分原始对象、初始化中的对象和最终引用,才能避免过早访问数据库或启动后台线程。
构造器适合接收必需的协作者并建立基本状态。依赖准备好后的初始化可以使用 @PostConstruct,但它由相应后处理器参与执行,不是所有初始化都集中在这里。InitializingBean、指定 initMethod 和 BeanPostProcessor 各有作用时机,学习流程图时要看它描述哪一种创建路径,别把简图当成所有 Bean 都必须完全相同的顺序。
AOP 代理可以理解为业务对象外面的一层处理入口:调用先经过它,符合规则时才会执行事务等额外处理。初始化回调通常直接作用于目标对象;对象用 this 调自己的方法,也不会自动绕到外面的代理。因此,在 @PostConstruct 中调用自己的 @Transactional 方法,不能指望这次内部调用通过代理开启事务。
文中这几个词,先认一认
- 目标对象
- 真正保存业务方法实现的那个对象,调用者拿到的可能是包在外面的代理。
- 后处理器
- 在 Spring 创建 Bean 的各个阶段参与检查、调整或包装对象的扩展组件。
- 销毁责任
- 明确谁负责在什么时候停止对象持有的线程,并关闭文件、连接等资源。
02看看它怎么工作
常见创建流程是:先创建对象、填好依赖,再进入 initializeBean。这个方法先执行部分 Aware 回调,让对象获得容器相关信息;随后运行初始化前处理器、初始化方法和初始化后处理器。
@PostConstruct 由相应的初始化前处理器识别并调用;InitializingBean 和自定义 initMethod 则属于后面的初始化方法部分。它们都参与准备对象,但触发位置不同,不能画成同一个回调。
常见的自动代理创建器会在初始化后,为业务对象加一层代理,因此调用者最后拿到的引用可能不是原对象。也有提前生成代理、在实例化前就直接提供替代对象等特殊路径。排查时先看调用者实际拿到了什么,再看调用有没有经过代理、方法能不能被增强。类代理不能增强 final 方法,JDK 代理通过接口工作;不能只看类上有注解,就认定所有方法都能获得额外处理。
资源初始化必须考虑部分成功。连接A已建立,线程池B启动失败时,应关闭已经拥有的A;关闭某资源又失败时,仍要继续清理其余资源并保存原因。正常容器关闭通常会调用受管单例销毁,prototype后续生命周期一般由使用者负责。强制终止不能保证销毁回调,因此不能把内存回调当成业务持久化保证。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
A["实例化目标"] --> B["填充依赖"]
B --> C["Aware与初始化前处理"]
C --> D["初始化方法"]
D --> E["初始化后处理"]
E --> F["暴露对象或代理"]
G["外部调用者"] --> F
F --> H["匹配通知与目标业务"]
C --> I["初始化内部this调用"]
I --> J["直接目标调用"]按顺序看,每一步做了什么
- 列出Bean持有的资源及其获得、释放责任。
- 定位初始化回调由哪个扩展点触发。
- 分别记录目标与最终暴露对象,观察代理类型。
- 从另一个Bean与this分别调用,比较通知和事务证据。
- 验证初始化失败时的清理及正常关闭时的资源释放。
03对照实现代码
Spring Framework v6.2.11 · AbstractAutowireCapableBeanFactory#initializeBean、#doCreateBean
原创教学实现,非源码节选。仅保留前处理、初始化与后处理的对象传递;未实现Aware、PostConstruct发现、synthetic分支、提前引用和代理生成。
import java.util.function.UnaryOperator;
class SourceModel {
interface Bean { void initialize(); }
static Bean initialize(Bean target, UnaryOperator<Bean> before,
UnaryOperator<Bean> after) {
// 前处理器可以参与准备,不能假定输入引用永远不变。
Bean prepared = before.apply(target);
prepared.initialize(); // 教学初始化动作,不等于所有Spring回调。
// 后处理器可以返回包装对象,调用者应使用最终引用。
return after.apply(prepared);
}
}doCreateBean在常见路径中完成实例创建、属性填充并调用initializeBean,单看构造器无法判断Bean是否完成管理。
initializeBean先处理部分Aware接口,再应用初始化前处理器,随后invokeInitMethods,最后应用初始化后处理器。
PostConstruct不应被误写为invokeInitMethods直接统一执行的所有内容;它由相关后处理器识别,具体扩展位置影响初始化调用行为。
方法返回 wrappedBean,说明容器最后交出的可能是原对象外面的包装。实例化前的特殊处理和提前暴露引用,还在其他代码路径中;不能只看这一个方法,就断言所有代理都一定在最后才创建。
代码先调用谁,再调用谁
createBeandoCreateBeanpopulateBeaninitializeBean- 初始化后处理返回最终引用
04动手写一个例子
资源启动失败的逆序回滚与清理异常保留
顺序获得若干资源,任何一步失败就逆序关闭此前成功资源;清理失败不阻断其他清理。成功后允许显式关闭一次,重复关闭无副作用。每个资源获得操作负责处理自身未完成的局部初始化。
- 输入 1
A成功、B获得失败- 输出 1
open:A,open:B,close:A
- 输入 2
A与B成功,B关闭失败- 输出 2
仍关闭A,并报告清理异常
资源成功返回后才加入拥有列表,使列表精确表达当前责任。后来失败时,从尾向前清理,符合后建立者可能依赖先建立者的顺序。
如果启动先失败,后来清理资源又失败,应把最初的启动异常保留为主要错误,把清理异常放进它的 suppressed 列表,作为附加原因。否则,最后一次 close 报错可能盖住真正的启动原因。若启动成功、只是显式关闭时出错,则单独收集并报告这些关闭错误。
关闭前先标记整体已关闭,保证重复close不会再次处理同一批资源。真实系统可能需要可重试释放或超时等待,那是额外契约,不能无条件重复执行带副作用的销毁。
为什么这样写是对的owned只包含已经成功获得且由本管理器负责的资源;回滚时每项恰好尝试关闭一次,任何单项失败不跳过其他资源。
每个资源至多获得一次并关闭一次;拥有列表和异常链最坏各N项。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
record Spec(String name, boolean failOpen, boolean failClose) {}
static class Resource {
final Spec spec; final List<String> log;
Resource(Spec spec,List<String> log) { this.spec=spec; this.log=log; }
void close() {
log.add("close:"+spec.name());
if (spec.failClose()) throw new IllegalStateException("close:"+spec.name());
}
}
static class Group implements AutoCloseable {
final List<Resource> owned=new ArrayList<>();
boolean closed;
static Group open(List<Spec> specs,List<String> log) {
Group group=new Group();
try {
for (Spec s:specs) {
log.add("open:"+s.name());
if (s.failOpen()) throw new IllegalStateException("open:"+s.name());
// 只有完整获得后才转移资源所有权。
group.owned.add(new Resource(s,log));
}
return group;
} catch (RuntimeException primary) {
group.release(primary);
throw primary; // 保持最初失败,清理原因附加其上。
}
}
void release(RuntimeException primary) {
if (closed) return;
closed=true;
for (int i=owned.size()-1;i>=0;i--) {
try { owned.get(i).close(); }
// 单项关闭失败不能阻止更早资源的释放。
catch (RuntimeException error) { primary.addSuppressed(error); }
}
}
public void close() {
if (closed) return;
RuntimeException failures=new IllegalStateException("cleanup");
release(failures);
if (failures.getSuppressed().length>0) throw failures;
}
}
static Spec ok(String name) { return new Spec(name,false,false); }
static void eq(Object expected,Object actual) {
if (!Objects.equals(expected,actual)) throw new AssertionError(actual);
}
public static void main(String[] args) {
List<String> log=new ArrayList<>();
Group g=Group.open(List.of(ok("A"),ok("B")),log);
g.close(); g.close();
eq(List.of("open:A","open:B","close:B","close:A"),log);
log.clear();
try { Group.open(List.of(ok("A"),new Spec("B",true,false)),log); }
catch (IllegalStateException e) { eq("open:B",e.getMessage()); }
eq(List.of("open:A","open:B","close:A"),log);
log.clear();
try {
Group.open(List.of(new Spec("A",false,true),new Spec("B",true,false)),log);
throw new AssertionError("expected startup failure");
} catch (IllegalStateException e) { eq(1,e.getSuppressed().length); }
log.clear();
Group h=Group.open(List.of(ok("A"),new Spec("B",false,true)),log);
try { h.close(); throw new AssertionError("expected close failure"); }
catch (IllegalStateException e) { eq(1,e.getSuppressed().length); }
eq(List.of("open:A","open:B","close:B","close:A"),log);
Group.open(List.of(),new ArrayList<>()).close();
System.out.println("PASS 5 resource lifecycle scenarios");
}
}这些特殊情况也要试一试
- 空资源组可以关闭,重复关闭不重复产生动作。
- 当前获得操作失败且尚未返回时,它负责自身局部清理;管理器只清理已接管项。
- 关闭异常以suppressed保留,其他资源仍继续逆序清理。
进阶选读 · 有依赖资源图的关闭与异步排空
场景HTTP入口、任务队列和数据库池共同关闭,需要先停止新工作,再等待存量任务完成。
做法区分停止接收、排空、释放三个阶段,按依赖图逆序处理;为每阶段设置有界等待与未完成工作记录,不能无限等待一个故障资源。
复杂度图排序O(V+E),实际时间取决于排空预算与资源关闭耗时。
好处与代价有界关闭可能留下未完成工作,业务可靠性必须依靠持久化和恢复协议,不能仅靠销毁回调。
05项目里怎么用
不要在构造器或初始化回调中无限等待网络服务。需要启动时确认的必要条件应有明确超时和失败信息;可延迟加载的资源也要对首次使用失败做受控处理。初始化里触发带事务的业务动作时,使用适当启动机制调用另一个已受管Bean,并确认实际经过代理。
代理问题应从调用路径验证。类上存在注解不证明通知已执行,this调用、手动new、方法可见性和final限制都可能改变结果。可以记录通知入口和实际数据库事务行为,避免只用代理类名作为全部证据;代理存在也不代表每个方法都匹配切点。
正常关闭资源和恢复业务,是两类工作。线程、连接和文件应及时关闭,订单与消息处理结果则应可靠保存并能重试。应用被强制结束时,@PreDestroy 不保证完整执行,不能靠它兜底保存关键业务。prototype 对象后续持有的资源,也应由使用方通过明确关闭接口管理。
容易踩的坑
- 把构造完成当成最终代理已经对外暴露。
- 在@
PostConstruct内部this调用期待新代理事务。 - 认为prototype资源一定由容器在使用完自动销毁。
- 清理异常覆盖主异常,导致真正启动原因丢失。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 构造器建立必需状态 | 依赖显式,普通测试容易。 | 不适合漫长网络初始化。 | 纯对象关系与本地不变量。 |
| 初始化回调 | 依赖已经可用,可准备资源。 | 影响启动时延与失败路径。 | 有界必要资源准备。 |
| 就绪后业务编排 | 可通过其他受管Bean使用增强能力。 | 需明确失败对就绪状态的影响。 | 需要容器完整能力的启动用例。 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01目标对象与暴露对象必定相同吗?
展开参考答案与推导
不一定,后处理器可能包装代理或提供其他引用。
02PostConstruct等同于所有初始化方法吗?
展开参考答案与推导
不是,它由相关后处理能力识别,InitializingBean和自定义initMethod另有执行位置。
03正常销毁回调能保证强制终止时订单落库吗?
展开参考答案与推导
不能,业务持久性需要独立存储与恢复机制。
换个情况,还会处理吗
01初始化里调用自己的事务方法无效怎么办?
展开参考答案与推导
检查this路径与代理时机,改为适当启动阶段调用另一个受管Bean并验证事务。
02创建A成功、创建B失败,A泄露怎么办?
展开参考答案与推导
明确资源所有权并在失败路径逆序清理已接管资源。
03close异常掩盖启动错误怎么办?
展开参考答案与推导
保留启动异常作为主原因,清理失败作为suppressed附加并继续清理。
写代码试一试
01怎样验证重复关闭无副作用?
- 输入 / 条件
资源组close两次- 输出 / 预期
每项close日志一次
提示 先标记关闭状态。
展开参考答案与推导
比较两次调用后的轨迹与一次调用一致。
02清理B失败后A是否还要关闭?
- 输入 / 条件
A、B已获得,B.close失败- 输出 / 预期
仍出现close:A
提示 每项单独捕获异常。
展开参考答案与推导
把关闭放入循环内try/catch,汇总错误后再报告。
03如何支持关闭预算?
- 输入 / 条件
某资源迟迟不结束- 输出 / 预期
到预算后报告未完成项
提示 不要无限阻塞关闭链。
展开参考答案与推导
将停止、排空和释放拆分并设置截止时间,保留未完成资源与业务恢复信息。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能沿对象生命周期标记资源准备、代理暴露与业务增强真正发生的位置。
本篇重点加练
重点核验代理对象与资源所有者,避免容器关闭后仍保留不应继续使用的对象。
- 目标对象与代理
- 单例与原型销毁责任
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为一个构造器注入依赖且具有初始化回调的 Bean 画生命周期,标出
BeanPostProcessor能观察或替换对象的阶段,以及需要核实的实际框架配置。- 提交内容
- 阶段图和扩展点职责表。
- 怎样算完成
- 构造注入初始化不合并成一步
- 观察对象与最终暴露对象区分
练到的知识点实例化与依赖注入 · 初始化回调 · BeanPostProcessor
- 作业 02排错实验
给服务公开方法添加计时通知,通过容器取得对象并与显式 new 的对象分别调用;记录代理类型、切点匹配和调用次数。
- 提交内容
- 最小 AOP 工程、自写调用检查与运行记录。
- 怎样算完成
- 通知是否执行有直接证据
- 切点条件与调用入口清楚
练到的知识点目标对象与代理 · 切点和通知
综合应用
- 作业 03面试场景
初始化回调调用本对象带事务的导入方法,团队认为启动时必然受事务保护。设计核验代理形成时机、调用路径和实际事务状态的实验。
- 提交内容
- 诊断图与验收证据清单。
- 怎样算完成
- 初始化内部调用单独分析
- 不由注解静态存在推断行为
练到的知识点初始化内部调用限制
- 作业 04设计评审
一个 singleton 管理连接资源,prototype 创建临时文件;应用既可能正常关闭也可能被强制结束。制定创建者、使用者和释放者的职责。
- 提交内容
- 资源责任表和关闭合同。
- 怎样算完成
- 作用域不代替资源所有权
- 正常与异常终止的保证分开
练到的知识点单例与原型销毁责任 · 正常关闭与共享状态
- 作业 05编程实践
实现启动三项本地资源的管理器,第二或第三项可注入失败,释放操作也可注入失败;验证已成功创建资源的最终状态和原始失败信息。
- 提交内容
- Java 实现、自写获取/清理失败及重复关闭测试、运行证据。
- 怎样算完成
- 资源释放次数可观察
- 失败信息可追溯
- 未创建资源不被误释放
练到的知识点实例化与依赖注入 · 初始化回调 · 单例与原型销毁责任 · 正常关闭与共享状态
- 作业 06排错实验
建立一个有关闭回调的服务,通过代理暴露并由调用方保存引用;正常关闭容器后执行合同允许的观察,记录目标、代理与资源状态。
- 提交内容
- 本地工程、自写生命周期检查和运行证据。
- 怎样算完成
- 对象身份与资源可用性分别核对
- 关闭后操作不产生外部副作用
练到的知识点目标对象与代理 · 单例与原型销毁责任
- 作业 07设计评审
测试直接 new 服务,运行时却从容器取得代理,双方行为不同。制定哪些测试验证业务方法、哪些验证通知与代理边界的分工。
- 提交内容
- 测试范围图和代表场景。
- 怎样算完成
- 目标逻辑与增强行为都有证据
- 没有用一层测试替代另一层
练到的知识点目标对象与代理
- 作业 08面试场景
两个 Bean 都包装同一个第三方客户端,并各自注册销毁回调;其中一个还允许独立提前关闭。制定资源所有权和重复关闭政策,说明需要哪些调用证据。
- 提交内容
- 共享资源图、关闭事件序列与验收条件。
- 怎样算完成
- 多拥有者歧义得到明确处理
- 独立关闭与容器关闭均被覆盖
练到的知识点单例与原型销毁责任
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
完成并发生命周期后:热重载替换一个共享客户端,旧请求仍在使用旧实例。设计发布新实例、停止新借用与关闭旧资源的合同。
- 提交内容
- 切换时序图与并发验收条件。
- 怎样算完成
- 对象发布与销毁边界分别控制
- 新旧实例身份可追踪
练到的知识点BeanPostProcessor · 目标对象与代理 · 切点和通知 · 正常关闭与共享状态
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
某 Bean 从普通对象改为代理对象,又开始包装第三方连接资源。审查比较身份、缓存引用和销毁操作的原假设,提出升级验收。
- 提交内容
- 变更影响表和生命周期测试矩阵。
- 怎样算完成
- 外层对象与底层资源关系明确
- 升级结论不只依赖启动成功
练到的知识点目标对象与代理 · 单例与原型销毁责任
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Spring Framework:Bean 生命周期 (新标签页)↗
阅读初始化、销毁回调与生命周期接口,理解业务调用前后的资源管理。
- 实践参考文档英文
Spring Framework:代理机制 (新标签页)↗
阅读 JDK
/CGLIB代理和内部调用限制,把增强是否生效落实到调用路径。 - 深入参考文档英文
Spring Framework:Bean 作用域 (新标签页)↗
比较 singleton、prototype 与请求作用域,检查对象状态和销毁责任。
- 深入参考文档英文
Spring Framework:IoC 容器 (新标签页)↗
按对象定义、依赖注入和容器扩展章节阅读,建立 Bean 管理的完整视角。
- 深入参考文档英文
Spring6.2 Bean生命周期 (新标签页)↗
阅读初始化、销毁与各类回调的责任。
- 深入参考文档英文
Spring6.2代理机制 (新标签页)↗
核对this调用和JDK/类代理限制。
- 深入参考文档英文
Spring6.2作用域 (新标签页)↗
确认prototype的后续销毁责任。
- 深入参考文档英文
Bean工厂源码 (新标签页)↗
追踪创建、填充、初始化和包装对象返回。