基于 @Transactional 的聲明式事務原理剖析
面試中經常會被問到:“為什么 Spring 通過一個注解就可以實現事務管理呢?”,一般大家聽到這個問題都會回答:因為 Spring 是通過 AOP 實現的。當再被追問道“那你能詳細說一下 @Transactional 的工作機制嗎?它是如何通過 AOP 控制事務的?”的時候,很多小伙伴就開始支支吾吾,亂說一通了 ??。
在上一篇文章Spring 是如何管理事務的當中,我們討論了有關于事務的一些基本概念以及 Spring 管理事務的兩種方式——編程式和聲明式,并對 @Transactional 注解的源碼及簡單使用進行了介紹。今天,我們書接上回,在源碼的層面上對 @Transactional 注解的工作原理進行分析,希望通過下面的分析,讓更多的小伙伴了解到 Spring 聲明式事務的工作原理,讓大家在面試時更從容的回答上述面試問題。
Tip:閱讀本篇內容可能需要小伙伴們對 Spring Bean 的生命周期以及 Spring AOP 有些基本的了解。
好的,開始之前我們先明確一下 Spring 使用 @Transactional 注解管理事務兩個重要內容,后邊我們根據這兩點來逐步揭開它背后的神秘:
- @Transactional 注解的解析時機?既然大家都知道原理是通過 AOP 實現的,那么 Spring 何時解析的該注解并生成的代理對象呢?
- 當我們業務代碼執行到事務方法時,代理對象是如何介入并將事務管理起來的?
代理生成
在 Spring Bean 的生命周期中,創建 Spring Bean 時有幾個重點方法:
- createBeanInstance():構造實例化對象
- populateBean():屬性裝配
- initializeBean():Bean 的初始化
當一個 Spring Bean 被創建時,它會依次執行這些方法。在這個階段,Spring 提供了非常多的擴展點來插手我們的 Bean 創建過程,而 Spring AOP 就是在執行到 initializeBean() 方法時,通過 BPP(BeanPostProcessor)Bean 后置處理器來實現的。
下面我們通過源碼來逐步分析下(源碼出自 spring-framework-5.3.33),源碼中我們只分析與本文相關的核心邏輯:
在 initializeBean() 方法中,執行了 Bean 的初始化前處理和后處理,其中 AOP 是在后處理中完成的。
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
// 執行部分Aware方法
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
} else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 初始化前處理
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 執行 Bean 的初始化
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
/**
* 執行 Bean 的初始化后處理
* AOP 配置方式 DefaultAdvisorAutoProxyCreator 在這里開始介入
*/
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
applyBeanPostProcessorsAfterInitialization() 方法中對 Bean 進行了后置處理,處理 AOP 的實現類為 DefaultAdvisorAutoProxyCreator,BPP 接口的方法實現最終會找到其父類 AbstractAutoProxyCreator。
AbstractAutoProxyCreator 中對接口方法的實現如下:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 重點關注 wrapIfNecessary 方法
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
wrapIfNecessary() 這個方法的作用就是判斷下當前的 Bean 是否需要生成代理,需要的話,生成一個代理對象返回。那么,我們被 @Transactional 注解標記的類肯定是要生成代理才能執行事務邏輯的,我們繼續往下分析,看看何時解析的 @Transactional 注解。
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;
}
// 關鍵方法,這里查找是否有匹配的 Advisor,有就創建代理
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;
}
// 標記該類不需要加強,并返回普通的 bean 實例
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
上述邏輯在查找 Advisor,什么是 Advisor?
Advisor 接口是 Spring AOP 中的一個頂層抽象,用于將切點(Pointcut)和通知(Advice)組合關聯起來。Advisor 接口的設計目的并不是為了提供給 Spring 用戶使用的,而是為了支持不同類型的 Advice,它作為一種內部機制來確保不同類型的 Advice 能夠有一致的支持方式。
實踐中,我們更關注它的子接口 PointcutAdvisor。PointcutAdvisor 繼承自 Advisor 接口并增加了 getPointcut() 方法,PointcutAdvisor 通過其持有的 Pointcut 來決定是否對某個連接點(方法調用)應用其 Advice。所以說,這個接口它確保了切點和通知之間的正確關聯,它是 Advice + Pointcut 組合更好的詮釋。
getAdvicesAndAdvisorsForBean() 方法是一個模板方法,由其子類 AbstractAdvisorAutoProxyCreator 實現,并調用了本類的 findEligibleAdvisors() 方法:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
/**
* 此處方法調用不再深入,主要目的是獲取當前容器所有的 advisors。
* 通過 getBean() 方法,獲取容器中所有 Advisor 接口類型的 Bean 的集合
*/
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 從獲取到的 Advisor 集合中獲取當前 Bean 對象適用的 Advisors
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 對advisor進行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
findEligibleAdvisors() 方法通過 getBean() 獲取了容器中所有 Advisor 類型的 Bean,然后內部調用 findAdvisorsThatCanApply() 方法從已獲取到的 Advisor 集合中找到可以適配當前 Bean 對象的那些 Advisor,該方法內部最終是通過層層調用 AopUtils 的重載方法 canApply() 實現的:
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
} else if (advisor instanceof PointcutAdvisor) {
// 處理事務的 Advisor 為 BeanFactoryTransactionAttributeSourceAdvisor
// 該類的父類實現了 PointcutAdvisor 接口
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
} else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
執行到此處后,我們找到了處理事務的 Advisor 為 BeanFactoryTransactionAttributeSourceAdvisor,然后再次執行重載的 canApply() 方法,來判斷給定的切點(pca.getPointcut())是否適用于給定的類(targetClass),這里通過 pca.getPointcut() 獲取到的切點為 TransactionAttributeSourcePointcut:
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
// 首先通過類過濾器進行匹配,檢查目標類是否符合要求
if (!pc.getClassFilter().matches(targetClass)) {
// 不符合,表示該 Pointcut 不能應用于此目標類
return false;
}
// 這里獲取切點上的方法級別的匹配器
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
// 創建一個 Set 集合來保存目標類和其所有接口
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 (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
// 使用方法匹配器進行逐個匹配
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
TransactionAttributeSourcePointcut 這個切點類實現了 MethodMatcher 接口并重寫了 matches 方法,所以執行方法匹配時,會走到如下邏輯:
public boolean matches(Method method, Class<?> targetClass) {
/**
* TransactionAttributeSource 是一個獲取事務屬性的策略接口(事務屬性為注解中的屬性配置)
* 這里獲取到的接口實現為 AnnotationTransactionAttributeSource
* 該類用于解析 @Transactional 注解,并根據注解中的配置(例如傳播行為、隔離級別等)
* 構建相應的 TransactionAttribute 對象
*/
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
調用 AnnotationTransactionAttributeSource 的 getTransactionAttribute() 方法來解析事務屬性,此方法由其父類 AbstractFallbackTransactionAttributeSource 實現,該方法邏輯比較簡單,內部主要是通過調用本類的 computeTransactionAttribute() 方法,我們直接來看下后邊這個方法:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 非 public 修飾的直接返回 null,返回 null 意味著方法匹配器沒有匹配成功,也就是該切面不會應用到這個 Bean,也就不會生成代理
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;
}
}
// 如果經過上述所有步驟后仍未找到事務屬性,則返回null,表示該方法不支持事務管理
return null;
}
通過理解這段代碼的工作機制,可以看出:
- 非 public 方法是不會生成代理的,所以這里解釋了為什么 @Transactional 注解加在非public 方法事務會失效的原因;
- 同時,從查找事務屬性的執行邏輯來看,這段代碼也證明了 @Transactional 注解方法級別的配置優先于類級別的配置。
上述邏輯中的 findTransactionAttribute() 方法也是一個模板方法,這里會調用至 AnnotationTransactionAttributeSource 類中,這個方法有兩個重載版本,分別用來處理方法和類級別的注解屬性解析,它們方法內部都調用了同一個方法來進行屬性解析:
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
return determineTransactionAttribute(clazz);
}
protected TransactionAttribute findTransactionAttribute(Method method) {
return determineTransactionAttribute(method);
}
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
// 遍歷解析器進行注解解析,有一個解析器解析成功便直接返回
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
determineTransactionAttribute() 方法中開始遍歷解析器對事務注解進行解析,解析器有 3 種:
- Ejb3TransactionAnnotationParser:用于解析 EJB 3 標準中的 javax.ejb.TransactionAttribute 注解。
- JtaTransactionAnnotationParser:用于解析 JTA 1.2 規范下的 javax.transaction.Transactional 注解。
- SpringTransactionAnnotationParser:用于解析 Spring 框架的 @Transactional 注解。
這里解析 @Transactional 注解的解析器為 SpringTransactionAnnotationParser。我們繼續看下 SpringTransactionAnnotationParser 中 parseTransactionAnnotation() 方法的解析邏輯:
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
// 從目標元素上獲取指定類型的注解屬性,這里注解是 Transactional
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
// 獲取到調用重載方法執行解析
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
parseTransactionAnnotation() 方法中又調用了其重載方法,執行具體的屬性解析邏輯,下面我們看下這段解析代碼的邏輯,相信大家看到這里應該會非常熟悉,這里解析的就是我們在 @Transactional 注解中配置的屬性:
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation"); // 事務傳播方式
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation"); // 事務隔離級別
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue()); // 事務超時時間
String timeoutString = attributes.getString("timeoutString");
Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0,
"Specify 'timeout' or 'timeoutString', not both");
rbta.setTimeoutString(timeoutString);
rbta.setReadOnly(attributes.getBoolean("readOnly")); // 只讀事務
rbta.setQualifier(attributes.getString("value")); // 事務管理器
rbta.setLabels(Arrays.asList(attributes.getStringArray("label")));
// 回滾相關的設置
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
好了,到這里,判斷 BeanFactoryTransactionAttributeSourceAdvisor 這個 Advisor 能否應用到當前 Bean 的邏輯就完成了,如果能夠成功解析到事務屬性返回值就不為 null,那么方法匹配器就會返回 true,canApply() 方法也就返回了 true,也就代表這個 Advisor 是可以應用到當前 Bean 的,接下來就可以創建代理了。
我們可以回到上面 AbstractAutoProxyCreator 類的 wrapIfNecessary() 方法中看下,如果 getAdvicesAndAdvisorsForBean() 返回不為空,那么就要為當前 Bean 創建代理,執行 createProxy() 方法,方法邏輯如下:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
/**
* 下邊這塊邏輯主要是在判斷代理要基于類代理還是基于接口進行代理
* 首先,檢查是否設置了 proxyTargetClass = true 屬性(即 CGLIB 代理),這個屬性的兩種設置方式:
* 注解方式:@EnableAspectJAutoProxy(proxyTargetClass = true)
* xml 配置方式:<aop:aspectj-autoproxy proxy-target-class="true" />
*/
if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
// 在創建新的代理時,也要將該類實現的所有接口包含進來,代理類要保持類的原有行為
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
}
}
}
// 如果開發時沒有明確要求使用 CGLIB 代理,也就是要走接口代理(JDK 代理)
else {
/**
* 檢查是否設置了 preserveTargetClass 屬性,這個屬性可通過 BeanDefinition 進行設置
* 如果 BeanDefinition 中設置了該屬性,那么這里要使用 CGLIB 代理
* Tip:熟悉 Bean 生命周期的小伙伴兒們可能知道,我們可以通過 BeanFactoryPostProcessor 來
* 干預 Bean 實例化過程,調整 Bean 的元數據,也就是 BeanDefinition
*/
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
/**
* 評估是否使用接口代理
* 檢查目標類有符合要求的接口:
* 如果有,則將目標類的接口設置到代理工廠的 interfaces 屬性,執行 JDK 動態代理
* 如果沒有,則設置 proxyTargetClass = true,執行 CGLIB 代理
*/
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 將之前查找到的 Advisor 設置到代理工廠
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
// 可以通過繼承該類來自定義代理工廠,這里是預留的模板方法
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
// 生成代理對象并返回
return proxyFactory.getProxy(classLoader);
}
通過上面源碼分析得知,createProxy() 方法的主要任務是:
- 創建代理工廠,并根據用戶配置和類的接口信息等來判斷應該使用 CGLIB 代理還是 JDK 代理來創建代理對象;
- 代理工廠使用已設置好的屬性參數來生成動態代理對象并返回。
再往后面就是使用具體的 AOP 代理工廠生成代理對象的邏輯了,這里我們不再繼續跟蹤源碼了,感興趣的小伙伴兒們可以自己再往后跟蹤分析一下。
其實,執行到這里的話,我們就知道了,在 Bean 的初始化過程中,Spring 已經對包含 @Transactional 注解的 Bean 進行了解析并生成了代理對象存儲到了容器中。
事務管理
既然代理對象已經生成了,那么熟悉代理模式的小伙伴兒就知道了,我們在執行目標方法的時候就會導向到代理對象中定義的行為中去,具體如下:
- CGLIB 代理:會導向到 MethodInterceptor 接口的 intercept() 方法中
- JDK 代理:會導向到 InvocationHandler 接口的 invoke() 方法中
上述創建代理邏輯中通過 proxyFactory.getProxy(classLoader); 去獲取代理對象時,實際生產代理對象的代理類有兩個默認實現 CglibAopProxy 與 JdkDynamicAopProxy,具體執行哪個類去生產代理對象也是通過 proxyTargetClass 屬性與目標類是否實現接口來判斷的。下邊我們以 JdkDynamicAopProxy 這個代理工廠為例看下 invoke() 的核心執行邏輯:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// ......
// 如果配置了 expose-proxy 屬性為 true,通過 ThreadLocal 保存到上下文中
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// 獲取針對此方法的攔截器鏈
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 實例
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 通過攔截器鏈執行方法調用
retVal = invocation.proceed();
}
return retVal;
}
}
在 invoke() 方法的邏輯中,我們可以看到其核心步驟是獲取適用于目標方法的攔截器鏈,并利用這個攔截器鏈作為參數來實例化一個 ReflectiveMethodInvocation 對象。隨后,通過調用該對象的 proceed() 方法,依次執行攔截器鏈中的每個攔截器,執行到最后調用我們的目標方法,后面我們具體來分析這個執行過程。
ReflectiveMethodInvocation 是 Spring AOP 內部用于表示一次具體方法調用的對象,它封裝了實際的目標方法調用,并且負責管理攔截器鏈的執行流程。
我們首先來看下獲取攔截器鏈的邏輯,getInterceptorsAndDynamicInterceptionAdvice() 方法最終調用至 DefaultAdvisorChainFactory 類的同名方法中:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) {
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// 先看類與切點是否匹配
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
// 獲取切點的方法匹配器
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
// 使用方法匹配器進行方法匹配
match = mm.matches(method, actualClass);
}
if (match) {
// 匹配成功后,通過適配器將 Advisor 轉換成 MethodInterceptor
// 并將所有攔截器加入到攔截器鏈中
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
/**
* 如果是運行時匹配,則創建一個新的動態方法匹配器實例并將其加入到攔截器鏈中
* 這個對象包含 MethodInterceptor 和 MethodMatcher 的實例
*/
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
// ......
// 返回構建好的攔截器鏈
return interceptorList;
}
getInterceptorsAndDynamicInterceptionAdvice() 方法負責構建適用于特定方法調用的攔截器鏈。該方法的主要任務是從 IOC 容器中獲取所有的 Advisor 對象,并根據目標對象及其方法進行篩選和適配,最終返回一個實現或者封裝了 MethodInterceptor 接口的對象組成的列表。
再來看下 proceed() 方法的邏輯:
public Object proceed() throws Throwable {
/**
* 索引變量,用來跟蹤當前正在處理的攔截器位置。每次調用 proceed() 方法時,它都會增加,指向下一個要執行的攔截器。
* 這里從索引為 -1 的攔截器開始調用按序遞增,并檢查是否已經到達攔截器鏈的末尾
* 如果當前索引已經是最后一個攔截器,則通過反射直接調用目標方法
*/
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 當攔截器鏈中的所有攔截器都已經被處理完畢后,調用此方法來執行實際的目標方法(即業務邏輯方法)。
// 這標志著攔截器鏈的結束
return invokeJoinpoint();
}
// 獲取下一個要執行的攔截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 如果是動態切點攔截器,需要在運行時評估其是否應該激活
// 靜態匹配部分已經在構建攔截器鏈時完成,并且匹配成功
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
// 使用方法匹配器匹配,判斷是否應用此攔截器
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
// 匹配成功,則調用該攔截器的 invoke 方法,繼續執行攔截器邏輯
return dm.interceptor.invoke(this);
}
else {
// 匹配失敗,跳過當前攔截器,遞歸調用 proceed() 繼續下一個攔截器
return proceed();
}
}
else {
// 直接調用 MethodInterceptor 的 invoke 方法
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
proceed() 方法的作用就是按順序逐個執行攔截器鏈中的攔截器。它從索引 -1 開始,逐個執行攔截器,直至執行到攔截器鏈的末尾后,完成對目標方法的調用。
好的,分析了這么多,我們的代理對象到底是如何將事務管理起來的呢?
事務的實現也是通過攔截器來實現的,這個攔截器是 TransactionInterceptor,它實現了 MethodInterceptor接口。那么,當目標方法為事務方法時,proceed() 方法中調用的攔截器的 invoke() 方法會調用到 TransactionInterceptor 類中:
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 調用內部方法 invokeWithinTransaction 來處理事務邏輯
// 回調接口用于提供具體的執行邏輯,包括如何繼續執行攔截器鏈或目標方法,以及訪問目標對象和方法參數等
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
// 繼續執行攔截器鏈中的下一個元素
return invocation.proceed();
}
@Override
public Object getTarget() {
// 返回當前調用的目標對象實例
return invocation.getThis();
}
@Override
public Object[] getArguments() {
// 返回傳遞給目標方法的參數數組
return invocation.getArguments();
}
});
}
可以看出,該方法處理帶有事務管理的目標方法調用主要是通過內部方法 invokeWithinTransaction() 實現的,這個方法會調用至其父類 TransactionAspectSupport 類中:
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)) { // 聲明式事務處理
// 開啟事務,將事務信息封裝成 TransactionInfo 對象
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 環繞通知,執行目標方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
/**
* 處理目標方法調用期間拋出的異常
* 根據在 rollbackFor 中指定的回滾的異常類型匹配,決定事務回滾或者提交
*/
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清理事務信息
cleanupTransactionInfo(txInfo);
}
// ......
// 在正常返回后提交事務
commitTransactionAfterReturning(txInfo);
return retVal;
} else { // 編程式事務處理
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();
try {
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
}
catch (Throwable ex) {
// 根據事務屬性判斷是否需要回滾
if (txAttr.rollbackOn(ex)) {
// 如果是運行時異常,直接拋出
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// 非回滾異常,記錄并返回 null
throwableHolder.throwable = ex;
return null;
}
}
finally {
// 清理事務信息
cleanupTransactionInfo(txInfo);
}
});
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
return result;
}
}
篇幅原因,我們就不再繼續深究事務的控制細節了。其實分析到這里的話,我們也基本了解了當一個帶有 @Transactional 注解的方法被調用時,聲明式事務究竟是如何被控制的。
那就是,Spring AOP 機制會使用代理對象來攔截事務方法的執行。最終通過 invokeWithinTransaction() 方法確保目標方法在一個適當的事務上下文中運行。它根據 @Transactional 中配置的事務屬性選擇合適的事務管理器,并決定是否需要創建或加入現有事務。然后,在事務邊界控制邏輯中,代理對象會在執行目標方法前開啟事務,在方法正常結束時提交事務;如果過程中拋出了異常,則根據事務屬性配置的規則進行回滾。整個過程自動完成了事務的開啟、提交和回滾。
總結
為了方便理解上述整個過程,請看下圖:
圖片