代理是谁织的、消息为何在提交后才发:一次读透 Spring AOP 自动代理与事务同步回调的源码实战
这是事务源码系列的第三篇。前两篇走通了运行期主干(拦截器五段式编排、传播行为 if-else 瀑布),但各留了一句话没展开:"剩下的是 Spring AOP 通用机制自动完成"------代理到底是谁、在什么时机织进 Bean 的?以及另一个实战刚需:事务提交后再发消息/刷缓存,怎么做到不"脏发"(先发消息后回滚)?两个问题一个答案在 spring-aop 的
AbstractAutoProxyCreator,一个在 spring-tx 的TransactionSynchronization。读完的共同感受:"自动"的真相是每个 Bean 初始化后问一遍"要不要wrap";"提交后再做"的真相是在 commit 时间线里给真正的doCommit前后插了四个钩子位。
S --- Situation:两句话能说对,代码在哪说不清
前两篇结尾各欠了一笔账:
- 注册篇(第二篇) :
@EnableTransactionManagement只是把"切点+通知"(BeanFactoryTransactionAttributeSourceAdvisor)放上了架,"代理由 AOP 通用机制自动织"------哪个类、什么时机、怎么判断"这个 Bean 需要代理"?三个问号都没兑现; - 实战刚需 :事务方法里发 MQ 消息,最怕的顺序事故是"消息先出去、事务后回滚"(消费方读不到数据);写在方法最后一行也不行------那行执行时
commit还没发生。正确姿势人人都说"提交后发",但"提交后"这个时机在代码里对应哪一行?
两个问题看似无关,实际都悬在同一个空白上:我们背熟了注解语义,却没见过装配和钩子的物理位置。这一篇就是去补这两个位置。
T --- Task:三个目标,一个约束
- 补齐织入链路:从 Bean 初始化完成到代理生成,说清入口方法、匹配过程(怎么找到 advisor)、代理选型(CGLIB 还是 JDK);
- 画完 commit 时间线 :把
TransactionSynchronization的注册时机和触发顺序钉到processCommit/processRollback的具体行号,标出"真正的数据库提交"前后的钩子位; - 说透
@TransactionalEventListener:它和TransactionSynchronization是什么关系,AFTER_COMMIT到底在哪个钩子位执行、没有事务时事件去哪了。
约束:延续系列标准------所有结论回溯到源码的具体方法;基线 Spring 5.x(spring-aop / spring-tx)。
A --- Action:一个后处理器、四步匹配、四个钩子位
第一步:每个 Bean 初始化完成后,都会被问一次"要不要包"
织代理的入口是 AbstractAutoProxyCreator#postProcessAfterInitialization------它是个 BeanPostProcessor,这意味着容器里每个 Bean 初始化完成后都要过这一关,没有白名单:
java
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey); // ← 每个Bean都问一遍
}
return bean;
}
AbstractAutoProxyCreator.java:299------名字直译就是"如果有必要就包一层"。
wrapIfNecessary(339 行起)做三件事:跳过已缓存/基础设施类(事务的三个 Bean 自己不会被再代理,防止无限套娃)→ getAdvicesAndAdvisorsForBean 找适用 advisor → 找到了就 createProxy。而第二篇里 AutoProxyRegistrar 注册的创建器是 InfrastructureAdvisorAutoProxyCreator------它只认 ROLE_INFRASTRUCTURE 的 advisor:
java
// 只认 BeanDefinition 角色为 ROLE_INFRASTRUCTURE 的 advisor
protected boolean isEligibleAdvisorBean(String beanName) {
return this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE;
}
InfrastructureAdvisorAutoProxyCreator.java:44------第二篇里事务 advisor 上那个 @Role(ROLE_INFRASTRUCTURE) 注解,在这里被消费。
但要注意这只是只有事务、没有任何 AOP 装配 场景下的创建器。AopConfigUtils 维护着一个优先级列表(AnnotationAware > AspectJAware > Infrastructure),registerOrEscalateApcAsRequired 注册时若发现已有更高优先级的创建器就替换而非共存 ------真实应用里(Spring Boot 的 AOP 自动装配、或 @EnableAspectJAutoProxy)实际跑的是 AnnotationAwareAspectJAutoProxyCreator,它的候选集是两层合并(90-103 行):
java
List<Advisor> advisors = super.findCandidateAdvisors(); // ① Spring 规则的 advisor Bean(含事务 advisor)
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); // ② 扫描 @Aspect 类组装 advisor
AnnotationAwareAspectJAutoProxyCreator.java#findCandidateAdvisors------本仓库注释原话:父类会查到 @EnableTransactionManagement 加进来的事务 advisor,这里再补上 @Aspect 切面。
事务 advisor 和 @Aspect 切面在同一次候选集扫描里汇合------@Role(ROLE_INFRASTRUCTURE) 这个标记仍是事务 advisor 进入候选集的门票(父类规则消费它),但最终织代理的创建器通常是 AnnotationAwareAspectJAutoProxyCreator,它两层都捞。
第二步:找 advisor 是四步流水线
子类 AbstractAdvisorAutoProxyCreator#findEligibleAdvisors 把"找"拆成四步(94-105 行):
java
List<Advisor> candidateAdvisors = findCandidateAdvisors(); // ① 容器里捞 advisor Bean
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(...); // ② 切点逐个方法匹配
extendAdvisors(eligibleAdvisors); // ③ 补一个默认 advisor
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors); // ④ 按 @Order 排序
}
AbstractAdvisorAutoProxyCreator.java#findEligibleAdvisors------③ 是补 ExposeInvocationInterceptor,④ 用 AnnotationAwareOrderComparator。
第②步是核心:AopUtils.findAdvisorsThatCanApply 会拿 advisor 的切点对目标类的每个方法 做匹配------对事务 advisor 来说,就是"这个方法上解析得出 @Transactional 元信息吗"。这也解释了第一篇的伏笔:切点不是静态的类过滤,而是每个 Bean 初始化时现场解析一次注解 。第④步的排序结果就是最终拦截器链的顺序------第二篇那句"事务拦截和 @Async、自定义切面共用同一条链,顺序由 advisor 的 order 决定",落点就在这一行。
第三步:CGLIB 还是 JDK,三行条件定生死
java
if (config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
... return new ObjenesisCglibAopProxy(config); // CGLIB:子类代理
}
else {
return new JdkDynamicAopProxy(config); // JDK:接口代理
}
DefaultAopProxyFactory.java:56-72 精简------选型就是一个 if。
日常默认走 hasNoUserSuppliedProxyInterfaces(业务类没实现接口)→ CGLIB。把整条织入链画出来:
#mermaid-svg-KK1l28GlzY5I3vhi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KK1l28GlzY5I3vhi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KK1l28GlzY5I3vhi .error-icon{fill:#552222;}#mermaid-svg-KK1l28GlzY5I3vhi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KK1l28GlzY5I3vhi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KK1l28GlzY5I3vhi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KK1l28GlzY5I3vhi .marker.cross{stroke:#333333;}#mermaid-svg-KK1l28GlzY5I3vhi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KK1l28GlzY5I3vhi p{margin:0;}#mermaid-svg-KK1l28GlzY5I3vhi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KK1l28GlzY5I3vhi .cluster-label text{fill:#333;}#mermaid-svg-KK1l28GlzY5I3vhi .cluster-label span{color:#333;}#mermaid-svg-KK1l28GlzY5I3vhi .cluster-label span p{background-color:transparent;}#mermaid-svg-KK1l28GlzY5I3vhi .label text,#mermaid-svg-KK1l28GlzY5I3vhi span{fill:#333;color:#333;}#mermaid-svg-KK1l28GlzY5I3vhi .node rect,#mermaid-svg-KK1l28GlzY5I3vhi .node circle,#mermaid-svg-KK1l28GlzY5I3vhi .node ellipse,#mermaid-svg-KK1l28GlzY5I3vhi .node polygon,#mermaid-svg-KK1l28GlzY5I3vhi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KK1l28GlzY5I3vhi .rough-node .label text,#mermaid-svg-KK1l28GlzY5I3vhi .node .label text,#mermaid-svg-KK1l28GlzY5I3vhi .image-shape .label,#mermaid-svg-KK1l28GlzY5I3vhi .icon-shape .label{text-anchor:middle;}#mermaid-svg-KK1l28GlzY5I3vhi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KK1l28GlzY5I3vhi .rough-node .label,#mermaid-svg-KK1l28GlzY5I3vhi .node .label,#mermaid-svg-KK1l28GlzY5I3vhi .image-shape .label,#mermaid-svg-KK1l28GlzY5I3vhi .icon-shape .label{text-align:center;}#mermaid-svg-KK1l28GlzY5I3vhi .node.clickable{cursor:pointer;}#mermaid-svg-KK1l28GlzY5I3vhi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KK1l28GlzY5I3vhi .arrowheadPath{fill:#333333;}#mermaid-svg-KK1l28GlzY5I3vhi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KK1l28GlzY5I3vhi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KK1l28GlzY5I3vhi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KK1l28GlzY5I3vhi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KK1l28GlzY5I3vhi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KK1l28GlzY5I3vhi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KK1l28GlzY5I3vhi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KK1l28GlzY5I3vhi .cluster text{fill:#333;}#mermaid-svg-KK1l28GlzY5I3vhi .cluster span{color:#333;}#mermaid-svg-KK1l28GlzY5I3vhi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KK1l28GlzY5I3vhi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KK1l28GlzY5I3vhi rect.text{fill:none;stroke-width:0;}#mermaid-svg-KK1l28GlzY5I3vhi .icon-shape,#mermaid-svg-KK1l28GlzY5I3vhi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KK1l28GlzY5I3vhi .icon-shape p,#mermaid-svg-KK1l28GlzY5I3vhi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KK1l28GlzY5I3vhi .icon-shape .label rect,#mermaid-svg-KK1l28GlzY5I3vhi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KK1l28GlzY5I3vhi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KK1l28GlzY5I3vhi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KK1l28GlzY5I3vhi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
无(eligibleAdvisors 为空)
有
是
否
Bean 初始化完成
postProcessAfterInitialization(BeanPostProcessor,每个Bean必经)
wrapIfNecessary
基础设施类?已缓存跳过?
findEligibleAdvisors 四步
原样返回
① 捞候选 advisor:Spring advisor Bean + @Aspect 切面(通常由 AnnotationAware...Creator 执行)
② 切点逐方法匹配(现场解析 @Transactional)
③ 补 ExposeInvocationInterceptor
④ @Order 排序 → 拦截器链
有命中的 advisor?
createProxy目标对象包成 SingletonTargetSource
实现了接口?
JDK 动态代理
CGLIB 子类代理
至此第一笔账还完:从 Bean 初始化到代理对象诞生,每一格都有方法名。
⚠️ 踩坑一:在 afterCommit 里抛异常------事务已提交,异常照样炸给你看
第二笔账从 processCommit 的时间线说起(784 行起)。把钩子位和真正的提交摆在一起,顺序一目了然:
java
prepareForCommit(status);
triggerBeforeCommit(status); // 钩子①:提交前(此时还能改数据)
triggerBeforeCompletion(status); // 钩子②:完成前
doCommit(status); // ★ 真正的 connection.commit()
try {
triggerAfterCommit(status); // 钩子③:提交后
}
finally {
triggerAfterCompletion(status, STATUS_COMMITTED); // 钩子④:完成(含提交/回滚两种)
}
AbstractPlatformTransactionManager.java#processCommit 精简------源码注释原话:钩子③抛的异常会传给调用方,但事务"仍被视为已提交"。
钩子③、④ 执行时 doCommit 已经完成------这就是"提交后再发消息"的正确时机:钩子③ 里的代码运行时,数据已真实落库,回滚不可能再发生,不存在脏发。但它有个反直觉的代价:钩子③ 里抛的异常不会 触发回滚(物理上不可能,已提交),却会一路抛给业务调用方------调用方看到异常,以为事务失败了,实际数据早已落库。源码注释对此写得很直白:"propagated to callers but the transaction still considered as committed"。
还有一个更隐蔽的坑:钩子③ 执行时 cleanupAfterCompletion(867 行)还没跑 ,连接还绑在 ThreadLocal 上------此时若在钩子里直接执行写 SQL,它跑在 autoCommit=false 的连接上,之后没有任何人替它 commit,写入悬空。教训一句话:afterCommit 钩子里只做"通知类"动作(发消息、清缓存),不要碰数据库。
第四步:@TransactionalEventListener------把"注册同步器"包装成了注解
实战里很少直接实现 TransactionSynchronization,更多用 @TransactionalEventListener(phase = AFTER_COMMIT)。它没有新机制,只是把"注册同步器"包装了一层(62-66 行):
java
public void onApplicationEvent(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 有事务:不发真监听器,而是注册一个同步器,事件先被"扣下"
TransactionSynchronizationManager.registerSynchronization(
createTransactionSynchronization(event));
}
else if (this.annotation.fallbackExecution()) {
processEvent(event); // 无事务且声明了兜底:立即处理
}
// 无事务且没声明兜底:事件被静默丢弃
}
ApplicationListenerMethodTransactionalAdapter.java#onApplicationEvent 精简。
被"扣下"的事件等到钩子位才真正执行。这里有个文档不显眼、读码才发现的细节:AFTER_COMMIT 阶段并不是挂在钩子③,而是挂在钩子④里按状态过滤:
java
@Override
public void afterCompletion(int status) {
if (this.phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
processEvent(); // ← AFTER_COMMIT 实际在 afterCompletion 里执行
}
else if (this.phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
processEvent();
}
...
}
同文件 115-125 行------AFTER_COMMIT/AFTER_ROLLBACK/AFTER_COMPLETION 三个阶段共用钩子④,按完成状态分流。
用一张时间线收束两笔账(同步器触发 ↔ 事务生命周期):
同步器钩子 数据库 processCommit 业务方法 同步器钩子 数据库 processCommit 业务方法 #mermaid-svg-r1EiIY4UpXDELNRy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-r1EiIY4UpXDELNRy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r1EiIY4UpXDELNRy .error-icon{fill:#552222;}#mermaid-svg-r1EiIY4UpXDELNRy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r1EiIY4UpXDELNRy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r1EiIY4UpXDELNRy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r1EiIY4UpXDELNRy .marker.cross{stroke:#333333;}#mermaid-svg-r1EiIY4UpXDELNRy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r1EiIY4UpXDELNRy p{margin:0;}#mermaid-svg-r1EiIY4UpXDELNRy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-r1EiIY4UpXDELNRy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-r1EiIY4UpXDELNRy .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-r1EiIY4UpXDELNRy .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-r1EiIY4UpXDELNRy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-r1EiIY4UpXDELNRy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-r1EiIY4UpXDELNRy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-r1EiIY4UpXDELNRy .sequenceNumber{fill:white;}#mermaid-svg-r1EiIY4UpXDELNRy #sequencenumber{fill:#333;}#mermaid-svg-r1EiIY4UpXDELNRy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-r1EiIY4UpXDELNRy .messageText{fill:#333;stroke:none;}#mermaid-svg-r1EiIY4UpXDELNRy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-r1EiIY4UpXDELNRy .labelText,#mermaid-svg-r1EiIY4UpXDELNRy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-r1EiIY4UpXDELNRy .loopText,#mermaid-svg-r1EiIY4UpXDELNRy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-r1EiIY4UpXDELNRy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-r1EiIY4UpXDELNRy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-r1EiIY4UpXDELNRy .noteText,#mermaid-svg-r1EiIY4UpXDELNRy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-r1EiIY4UpXDELNRy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-r1EiIY4UpXDELNRy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-r1EiIY4UpXDELNRy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-r1EiIY4UpXDELNRy .actorPopupMenu{position:absolute;}#mermaid-svg-r1EiIY4UpXDELNRy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-r1EiIY4UpXDELNRy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-r1EiIY4UpXDELNRy .actor-man circle,#mermaid-svg-r1EiIY4UpXDELNRy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-r1EiIY4UpXDELNRy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事务方法内发布事件→ 被扣下,注册为同步器方法正常返回,进入 commit① beforeCommit(数据未落库)② beforeCompletion★ doCommit(真正落库)③ afterCommit(发送通知类动作的安全位)④ afterCompletion(COMMITTED)→ AFTER_COMMIT 事件在此执行cleanupAfterCompletion(解绑连接)
对比一下"写在方法最后一行":那一步发生在 doCommit 之前,消息先出、事务可能回滚------脏发;钩子③④ 在 doCommit 之后,天然不脏发。
R --- Result:两笔账都还到了行号
| 结论 | 源码出处 | 状态 |
|---|---|---|
| 织代理入口 = BeanPostProcessor,每个 Bean 初始化后必经 | AbstractAutoProxyCreator#postProcessAfterInitialization 299 |
✅ |
| 真实应用的创建器是 AnnotationAwareAspectJAutoProxyCreator(优先级替换机制),候选集 = Spring advisor Bean + @Aspect 切面;ROLE_INFRASTRUCTURE 门票与第二篇 @Role 呼应 | AopConfigUtils 118-149 + AnnotationAwareAspectJAutoProxyCreator 90-103 + InfrastructureAdvisorAutoProxyCreator 44-47 |
✅ |
| 找 advisor 四步:捞→逐方法匹配→补默认→排序 | AbstractAdvisorAutoProxyCreator#findEligibleAdvisors 94-105 |
✅ |
| CGLIB/JDK 选型三条件 | DefaultAopProxyFactory#createAopProxy 56-72 |
✅ |
| 钩子③④ 在 doCommit 之后,异常抛出但事务已提交 | processCommit 815(doCommit)/ 849-858 |
✅ |
| 钩子③ 时连接仍绑 ThreadLocal,写 SQL 会悬空 | processCommit finally 867 才 cleanup |
✅ |
| AFTER_COMMIT 实际在钩子④按状态执行;无事务默认丢弃事件 | ApplicationListenerMethodTransactionalAdapter 115-125 / 67-78 |
✅ |
系列三篇至此闭环:注册期 (注解→三个 Bean→AOP 织代理,第二、三篇)→ 运行期 (五段编排→传播分派→doBegin/savepoint/suspend→业务→钩子收尾,第一、二篇)→ 收尾期(本篇钩子时间线)。
沉淀的三条原则
- 框架的"自动",去找它的循环边界------"自动织代理"的真相是 BeanPostProcessor 对每个 Bean 的无差别遍历;找到遍历发生在哪,"自动"就祛魅成 for 循环;
- "提交后再做"这类时序需求,去时间线里找钩子位而不是猜方法名------把
doCommit画在时间线中央,前两个钩子位能改数据、后两个只能发通知,一眼可判; - 跨模块协作的暗号常常是一个不起眼的角色标记------
ROLE_INFRASTRUCTURE一个 BeanDefinition 属性,串起了@EnableTransactionManagement和自动代理两套体系;读跨模块链路时,优先找这类标记。
遗留事项
两个点仍未展开,留作系列后续:钩子②与钩子④在异常路径上的差别触发表(processRollback 900-948 行对 beforeCompletionInvoked 的补偿逻辑);以及拦截器链的执行机制本身------ReflectiveMethodInvocation#proceed 的链式递归和 ExposeInvocationInterceptor 的作用,那是 AOP 的最后一块拼图,也适合作为独立一篇的主题。
本文全部基于开源框架(Spring)的公开源码研读,代码片段为源码节选精简,无敏感信息。