引子:代理明明在,方法上却没有事务
上一篇结束时,容器里备好了三样东西:自动代理创建器、事务
Advisor、TransactionInterceptor。但”备好”不等于”用上”,它们得先落到具体
Bean 上,才可能真的拦到一次调用。
于是有了本篇特有的两类困惑:有的 Bean
被包了一层,有的没有,判断标准是什么?更隐蔽的是
AopUtils.isAopProxy(bean) 返回
true,@Transactional
却没生效——代理在,只是这个方法上一条通知都没有。
本篇接第一篇 ,覆盖第三、四部分:wrapIfNecessary
的三道闸门、pointcut 如何判断”方法上有没有事务属性”、JDK 与 CGLIB
决定注解写在哪才有效、调用怎么进链。口径同导读:Spring Framework 6.1.8 +
Spring Boot 3.3.x(Java 17)。
本篇要回答的问题:
代理在哪一步生成?为什么 @PostConstruct 里的
this 不是代理?
事务的”切点表达式”为什么只有两行逻辑?注解写在接口和实现类上,结果为什么不一样?
代理在、方法上却没有通知,断在哪一环?
第三部分:代理创建阶段——哪些
Bean 会被包一层
3.1 时机:Bean 初始化的最后一步
自动代理创建器的核心方法就两个,先看调用点:
1 2 3 4 5 6 7 8 9 10 11 @Override public Object postProcessAfterInitialization (@Nullable Object bean, String beanName) { if (bean != null ) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this .earlyBeanReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
绕开 BeanPostProcessor 的整体列表,只看单个 Bean
的一生 ,顺序是这样的:
1 2 3 4 5 6 实例化(构造器 / 工厂方法) → 属性填充(依赖注入) → BeanPostProcessor.postProcessBeforeInitialization → @PostConstruct / InitializingBean.afterPropertiesSet() ← 你自己的初始化逻辑在这 → BeanPostProcessor.postProcessAfterInitialization ← 代理在这里诞生 → 这个 Bean 进入容器,可被别人注入
代理是在
@PostConstruct、afterPropertiesSet()
全部执行完之后才生成的。 所以 Bean
自己的初始化方法里,this
永远不是代理——它拿到的是刚造好的目标对象;而别人注入你的 Bean
时,拿到的才是代理。
还有一层细节:注册 APC 时,AopConfigUtils 会把它的
order 设成 Ordered.HIGHEST_PRECEDENCE:
1 2 3 4 5 6 7 RootBeanDefinition beanDefinition = new RootBeanDefinition (cls);beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order" , Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
这个值来自
ProxyProcessorSupport(AbstractAutoProxyCreator
的父类)的 order 字段,默认本是
LOWEST_PRECEDENCE。调成最高优先级,是为了让自动代理创建器在
BeanPostProcessor 列表里排在前面 ,这样它的
postProcessAfterInitialization
会先于其他后置处理器执行——后面 6.3
节(第四篇)会看到,@Async 正是利用”自己排在 APC
之后”这一点,往已经建好的代理里追加 advisor。
这也解释了为什么”在同一个 Bean
的方法里调用自己”必然绕过事务:this
在编译期就绑定到了目标对象,跟代理没有半毛钱关系。
关于 BeanPostProcessor 在容器里的完整位置,可以对照 Spring
接入点:跟着容器过一生
里的第三幕——那里用几十行手写代理演示了同一个位置。
3.2
wrapIfNecessary:三道闸门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 protected Object wrapIfNecessary (Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this .targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this .advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this .advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } Object [] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null ); if (specificInterceptors != DO_NOT_PROXY) { this .advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource (bean)); this .proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this .advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
三道闸门:
targetSourcedBeans :这个 Bean 是通过
TargetSource 暴露的,不重复包装。
advisedBeans
缓存 :已经判断过的直接复用结论。注意
advisedBeans.put(cacheKey, Boolean.FALSE)
这个”负缓存”——一个 Bean
一旦被判定为不需要代理,后面再有人问也是”不代理” 。
isInfrastructureClass /
shouldSkip :Advice、Pointcut、Advisor、AopInfrastructureBean
这些”基础设施类”本身不代理;shouldSkip 在
AspectJAwareAdvisorAutoProxyCreator
里还有额外职责——遍历候选 Advisor,跳过 @Aspect
类自己 ,否则切面会试图去代理它自己。
过了三道闸门,才是真正找 Advisor:
1 2 3 4 5 6 7 8 9 10 protected Object [] getAdvicesAndAdvisorsForBean ( Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) { List <Advisor> advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); }
返回 DO_NOT_PROXY
的那一刻,代理就不会产生 ,这个 Bean 上所有
@Transactional 一起失效。这就是失效点 1。
3.3 事务
Advisor 的 Pointcut:本质是”方法上有没有事务属性”
BeanFactoryTransactionAttributeSourceAdvisor
本身很简单,全部逻辑都在它持有的 pointcut 里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut (); public void setTransactionAttributeSource (TransactionAttributeSource transactionAttributeSource) { this .pointcut.setTransactionAttributeSource(transactionAttributeSource); } @Override public Pointcut getPointcut () { return this .pointcut; } }
这个 pointcut
是整个事务机制里最”反直觉”的一段代码,因为它只有两行有效逻辑 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Nullable private TransactionAttributeSource transactionAttributeSource; public TransactionAttributeSourcePointcut () { setClassFilter(new TransactionAttributeSourceClassFilter ()); } @Override public boolean matches (Method method, Class<?> targetClass) { return (this .transactionAttributeSource == null || this .transactionAttributeSource.getTransactionAttribute(method, targetClass) != null ); } }
事务的”切点表达式”就是”能解析出事务属性的方法”。
没有 @annotation(Transactional) 这样的表达式,没有
execution(* com.example..*Service.*(..))——它就是直接问属性源:“这个方法的
TransactionAttribute 是 null 吗?”
这里藏着一个非常重要的事实:判断”要不要切”和判断”用哪个事务配置”是同一件事 。所以属性解析为空
= 切不上 = 事务失效,一次调用就能确定因果,不需要分别排查。
类级过滤也做了一次排除:
1 2 3 4 5 6 7 8 9 10 11 12 13 private final class TransactionAttributeSourceClassFilter implements ClassFilter { @Override public boolean matches (Class<?> clazz) { if (TransactionalProxy.class.isAssignableFrom(clazz) || TransactionManager.class.isAssignableFrom(clazz) || PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) { return false ; } return (transactionAttributeSource == null || transactionAttributeSource.isCandidateClass(clazz)); } }
TransactionalProxy、TransactionManager、PersistenceExceptionTranslator
这三类被明确排除——避免给事务基础设施自己套事务代理 ,否则会无限套娃。最后还用
isCandidateClass
做一次廉价预筛,明显不可能有注解的类(比如各种 JDK
类)直接跳过注解解析。
3.4
属性解析:查找顺序决定了”注解写在哪才生效”
matches 里调的 getTransactionAttribute
是一个带缓存的装饰器,真正的解析在
computeTransactionAttribute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Override public TransactionAttribute getTransactionAttribute (Method method, @Nullable Class<?> targetClass) { if (method.getDeclaringClass() == Object.class) { return null ; } Object cacheKey = getCacheKey(method, targetClass); TransactionAttribute cached = this .attributeCache.get(cacheKey); if (cached != null ) { return (cached != NULL_TRANSACTION_ATTRIBUTE ? cached : null ); } } protected TransactionAttribute computeTransactionAttribute (Method method, @Nullable Class<?> targetClass) { if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null ; } Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); TransactionAttribute txAttr = findTransactionAttribute(specificMethod); if (txAttr != null ) { return txAttr; } txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } if (specificMethod != method) { txAttr = findTransactionAttribute(method); if (txAttr != null ) { return txAttr; } txAttr = findTransactionAttribute(method.getDeclaringClass()); if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { return txAttr; } } return null ; }
查找顺序拆成一张表:
顺序
查找目标
备注
0
非 public 方法直接短路
由 allowPublicMethodsOnly() 控制(见 3.5)
1
目标类的具体方法
AopUtils.getMostSpecificMethod
把接口方法映射到实现类方法
2
该方法的声明类
类级 @Transactional 生效点
3
原始方法(通常是接口方法)
仅当 specificMethod != method,即代理接口调用时
4
原始方法的声明类
接口上的类级注解
这段代码有两个必须记住的推论:
推论一:attributeCache 是
MethodClassKey
级别的缓存,而且缓存”没有事务”这个结论。
所以属性解析只在第一次调用时算一次。你不可能在运行时”动态决定”某个方法要不要事务——除非换掉属性源实现。
推论二:查找顺序 3、4 只在
specificMethod != method 时才走。
这意味着注解写在哪,和代理方式 强耦合,下一节展开。
3.5
代理方式:JDK 还是 CGLIB,直接决定注解写在哪才有效
先看代理创建期的类级匹配 。AopUtils.canApply
会同时扫描目标类和它实现的所有接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static boolean canApply (Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { Set <Class<?>> classes = new LinkedHashSet <>(); if (!Proxy.isProxyClass(targetClass)) { classes.add(ClassUtils.getUserClass(targetClass)); } classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); for (Class<?> clazz : classes) { Method [] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if (methodMatcher.matches(method, targetClass)) { return true ; } } } return false ; }
注意
getAllInterfacesForClassAsSet ——接口方法也会参与匹配。
再看运行期的按方法匹配 。代理收到调用后,AdvisedSupport
按 method 去找拦截器链:
1 2 3 4 5 6 public List <Object> getInterceptorsAndDynamicInterceptionAdvice (Method method, @Nullable Class<?> targetClass) { return this .methodCache.computeIfAbsent(new MethodCacheKey (method), k -> this .advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this , method, targetClass)); }
关键在于这个 method 从哪来:
JDK 动态代理 :代理实现的是接口,回调拿到的
method 就是接口方法 。属性解析走查找顺序 1
→ 2 → 3,第 3 步回退到接口方法,接口方法上的
@Transactional 能找到 。
CGLIB 代理 :代理是目标类的子类,回调拿到的
method
是目标类(或父类)声明的方法 ,specificMethod == method,于是查找顺序
3、4
根本不执行,只有当实现类方法或实现类上有注解时才生效 。
这正好解释了 Spring
官方文档那句容易被忽略的提醒:@Transactional
推荐写在实现类上;写在接口上”只在基于接口的代理时按预期工作”。这不是风格建议,而是
computeTransactionAttribute 的分支结构决定的。
而且 CGLIB
这条路上有一个很坑的现象:代理创建了,但事务没生效 。链条是这样断的:类级匹配扫描了接口方法(canApply
返回 true,代理照常生成),但运行期是按方法匹配的,而 CGLIB 回调收到的
method 是被重写的实现类方法 :
1 2 3 4 public Object intercept (Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { List <Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
于是 AopUtils.getMostSpecificMethod(method, targetClass)
返回的就是 method
本身,specificMethod == method
成立,computeTransactionAttribute
里那段”回退到接口方法”的分支根本不会执行 → 属性解析为 null
→ 拦截器链为空。此时你用 AopUtils.isAopProxy(bean)
检查,结果是 true——代理在,只是这条方法上没有任何通知。
代理方式还决定了哪些方法在物理上 无法被拦截:
方法修饰符
CGLIB
JDK 代理
说明
public
✅
✅
只要注解解析得出属性
protected
✅ 需代理类与被代理类同包(Spring 6+ 事务层不再拦截)
❌
接口方法必然 public
包级可见(默认)
⚠️ 同类加载器下可重写
❌
跨 ClassLoader 时 CGLIB 无法代理
private
❌
❌
子类无法重写,直接调目标
static
❌
❌
不经过实例调用
final 方法
❌
—
CGLIB 会打 debug 日志:“Final method cannot get proxied via
CGLIB”
关于 protected,Spring 6 的行为变化容易踩:
1 2 return new AnnotationTransactionAttributeSource (false );
1 2 return new AnnotationTransactionAttributeSource ();
所以在 Spring 6 + CGLIB + protected
方法 这个组合下,@Transactional
是能生效的 (前提依旧是 CGLIB
能重写它:生成的代理类要与被代理类同包);而 5.3
里同样的代码不生效。如果你照着老文章排查”protected
方法为什么不回滚”,先确认版本,别急着改代码。
顺带说明 CGLIB 是怎么”知道自己拦不住”的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (!Modifier.isStatic(mod) && !Modifier.isPrivate(mod)) { if (Modifier.isFinal(mod)) { if (logger.isWarnEnabled() && implementsInterface(method, ifcs)) { logger.warn("Unable to proxy interface-implementing method [" + method + "] because " + "it is marked as final, consider using interface-based JDK proxies instead." ); } if (logger.isDebugEnabled()) { logger.debug("Final method [" + method + "] cannot get proxied via CGLIB: " + "Calls to this method will NOT be routed to the target instance and " + "might lead to NPEs against uninitialized fields in the proxy instance." ); } } }
把日志级别开到
debug,“哪些方法拦不住”其实是Spring
自己会告诉你的 。这比到处加断点靠谱得多。
第四部分:运行阶段——一次调用穿过代理链
4.1
入口:代理对象如何找到拦截器链
以 JDK 代理为例,核心就三行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (this .advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true ; } target = targetSource.getTarget(); Class <?> targetClass = (target != null ? target.getClass() : null ); List <Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object [] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { MethodInvocation invocation = new ReflectiveMethodInvocation (proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } }
两处和事务直接相关:
chain.isEmpty() 就走
invokeJoinpointUsingReflection。 这就是失效点 2
的物理形态——代理明明在,只是这个方法上一条通知都没有,于是”和没代理一样”。
exposeProxy 决定了
AopContext.currentProxy() 能不能用。 它是
@EnableAspectJAutoProxy(exposeProxy = true)
打开的,默认关闭。很多人靠
((OrderService) AopContext.currentProxy()).saveOrder(...)
解决自调用,如果没开这个开关,会直接抛
IllegalStateException: Cannot find current proxy——症状看起来像”事务问题”,其实是代理配置问题。
4.2 拦截器链的顺序
ReflectiveMethodInvocation.proceed()
是一个标准的递归:
1 2 3 4 5 6 7 8 9 10 11 12 public Object proceed () throws Throwable { if (this .currentInterceptorIndex == this .interceptorsAndDynamicMethodMatchers.size() - 1 ) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this .interceptorsAndDynamicMethodMatchers.get(++this .currentInterceptorIndex); return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this ); }
链的构建由 DefaultAdvisorChainFactory 完成,它拿到的
Advisor 顺序决定了拦截器的顺序,而顺序由每个 Advisor 的
order 决定:升序执行,order
越小越靠外 。事务 Advisor 的默认 order 是
Ordered.LOWEST_PRECEDENCE ,也就是 int
的最大值——但先别急着下”事务一定在最内层”的结论:不写
@Order
的切面同样是这个值 ,两者并列时相对顺序在源码层面没有任何保证,7.2
节(第四篇)展开。
这一点在第七部分(第四篇)会结合一个具体例子说明。
4.3
TransactionInterceptor.invoke:只剩转发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor , Serializable { @Override @Nullable public Object invoke (MethodInvocation invocation) throws Throwable { Class <?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null ); return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback () { @Override public Object proceedWithInvocation () throws Throwable { return invocation.proceed(); } }); } }
它只做一件事:把 AOP 的 MethodInvocation
翻译成事务模块自己的 InvocationCallback,然后交给
TransactionAspectSupport.invokeWithinTransaction。
这里有一个很容易看漏的细节:targetClass 用的是
AopUtils.getTargetClass(invocation.getThis())。
传入属性解析的 targetClass
是目标类 ,不是代理类,也不是你声明的那个字段类型。所以
computeTransactionAttribute 里的
getMostSpecificMethod
才有意义——它要把接口方法映射到这个目标类上的具体实现。
4.4
invokeWithinTransaction:事务的心脏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 protected Object invokeWithinTransaction (Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null ); final TransactionManager tm = determineTransactionManager(txAttr); PlatformTransactionManager ptm = asPlatformTransactionManager(tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) { TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } else { } }
这段代码就是”事务”的全部语义 ,翻译成大白话:
解析事务属性,拿到 txAttr。
确定事务管理器 tm。
createTransactionIfNecessary:需要就开(或加入)事务。
调用链上的下一个拦截器,最终执行你的业务方法。
抛异常 →
completeTransactionAfterThrowing(决定回滚还是提交,然后原样抛出)。
正常返回 → commitTransactionAfterReturning。
finally 里 cleanupTransactionInfo 恢复
ThreadLocal 栈。
注意第 5
步:异常一定会被重新抛出 (throw ex)。事务切面本身从不吞异常,你之所以”看不到异常”,是自己写的
try-catch 吞掉的——这属于失效点 5。
再看”需要就开”这个措辞的由来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 protected TransactionInfo createTransactionIfNecessary (@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { if (txAttr != null && txAttr.getName() == null ) { txAttr = new DelegatingTransactionAttribute (txAttr) { @Override public String getName () { return joinpointIdentification; } }; } TransactionStatus status = null ; if (txAttr != null ) { if (tm != null ) { status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured" ); } } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }
tm == null 时只打一行 debug
日志,然后照常执行业务方法,不报错、不告警。
这是事务最危险的一种失效方式,也是失效点 4。生产环境通常开
INFO,这行日志永远不会出现。
要补一个前提:这个分支要求拦截器已经装配好、并且事务属性非空 。如果容器里连
TransactionManager Bean
都没有,TransactionAutoConfiguration 的
EnableTransactionManagementConfiguration
根本不会生效(第一篇 2.5 节),TransactionInterceptor
压根不会注册——那才是更彻底的静默,连这行 debug 都没有。
想在本地确认这种失效,把日志调成:
1 logging.level.org.springframework.transaction.interceptor =TRACE
TRACE 会打印
Getting transaction for [全限定方法名] /
Completing transaction for [...],有没有真的开事务一目了然。
4.5 事务管理器是怎么被选中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 protected TransactionManager determineTransactionManager (@Nullable TransactionAttribute txAttr) { if (txAttr == null || this .beanFactory == null ) { return getTransactionManager(); } String qualifier = txAttr.getQualifier(); if (StringUtils.hasText(qualifier)) { return determineQualifiedTransactionManager(this .beanFactory, qualifier); } else if (StringUtils.hasText(this .transactionManagerBeanName)) { return determineQualifiedTransactionManager(this .beanFactory, this .transactionManagerBeanName); } else { TransactionManager defaultTransactionManager = getTransactionManager(); if (defaultTransactionManager == null ) { defaultTransactionManager = this .transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY); if (defaultTransactionManager == null ) { defaultTransactionManager = this .beanFactory.getBean(TransactionManager.class); this .transactionManagerCache.putIfAbsent(DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); } } return defaultTransactionManager; } }
优先级很清楚:@Transactional(transactionManager = "...")
→ 拦截器上配的 Bean 名 → 容器里按类型取 。
最后那个 beanFactory.getBean(TransactionManager.class)
是多数据源场景下的经典陷阱:按类型取,容器里有多个候选就直接抛
NoUniqueBeanDefinitionException 。所以多数据源时必须用
transactionManager 属性显式指定——这和后端文章 Spring
中 MyBatis、MyBatis-Plus 是怎么样结合工作的? 里”每个数据源一套
DataSource + SqlSessionFactory +
TransactionManager“的结论是同一件事的两面。
到这里,调用确实进到事务了。接下来看事务管理器拿什么在记账。
小结
代理的生成与进链在这里走完:wrapIfNecessary
的三道闸门决定”包不包”,TransactionAttributeSourcePointcut
用”能不能解析出事务属性”决定”切不切”,而 AopUtils.canApply
扫接口、运行期却按方法匹配,于是有了”代理在、事务不在”。进链后
TransactionInterceptor
只做转发,tm.getTransaction(txAttr)
之后的”当前事务”记在哪,留给下一篇。
下一篇
(三):事务管理器的
ThreadLocal 账本 。系列入口见导读 。