在Spring事務用法示例與實現原理中我們講到,在進行tx:annotation-driven
標簽解析的時候,Spring注冊了三個bean:BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor和AnnotationTransactionAttributeSource。這里BeanFactoryTransactionAttributeSourceAdvisor本質上是一個Advisor,在Spring Aop中,Advisor封裝了切面環繞的所有信息,最主要的就是Advice和Pointcut。這里Advice中包含了需要環繞的切面邏輯,而Pointcut中則封裝了進行方法過濾的判斷條件,即用於判斷某個方法是否需要環繞當前切面邏輯的條件。關於這三個類的關系如下:
對應的,Spring事務中聲明的這三個bean就與切面環繞所使用的組織結構完全一致,這里TransactionInterceptor實現了Advice接口,進行事務切面環繞的邏輯也封裝在了這個bean中;AnnotationTransactionAttributeSource則封裝了目標方法是否需要進行事務邏輯環繞的判斷邏輯,實際上,其沒有實現Pointcut接口,但是BeanFactoryTransactionAttributeSourceAdvisor在進行目標方法判斷的時候實際上還是委托給了AnnotationTransactionAttributeSource進行。對於這幾個類的講解我們會依次進行,本文則主要講解AnnotationTransactionAttributeSource是如何判斷目標方法是否需要進行事務邏輯環繞的。
1. 切點聲明
在BeanFactoryTransactionAttributeSourceAdvisor中,其聲明了一個TransactionAttributeSourcePointcut類型的屬性,並且實現了其getTransactionAttributeSource()
方法,這個方法的返回值是一個TransactionAttributeSource類型的對象,而實際上,其返回的就是AnnotationTransactionAttributeSource。這里創建Pointcut的源碼如下:
@Nullable private TransactionAttributeSource transactionAttributeSource; private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { // 將標簽解析時注冊的AnnotationTransactionAttributeSource返回 protected TransactionAttributeSource getTransactionAttributeSource() { return transactionAttributeSource; } };
需要強調的是,這里返回的AnnotationTransactionAttributeSource就是在tx:annotation-driven
標簽解析時注冊的bean。既然BeanFactoryTransactionAttributeSourceAdvisor在其內部聲明了一個Pointcut對象,那么對於目標方法的匹配應該在Pointcut.matches()
方法中,也就是說Spring事務是否需要環繞切面邏輯的判斷就在TransactionAttributeSourcePointcut.matches()
中,如下是該方法的源碼:
@Override public boolean matches(Method method, @Nullable Class<?> targetClass) { // 如果目標類不為空,並且是已經使用Transaction環繞后生成的類,則會將其過濾掉 if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) { return false; } // 獲取TransactionAttributeSource對象,這個方法也就是上面一個代碼片段中實現的方法, // 也就是說這個方法將返回AnnotationTransactionAttributeSource TransactionAttributeSource tas = getTransactionAttributeSource(); // 通過TransactionAttributeSource獲取事務屬性配置,如果當前方法沒有配置事務,則不對其進行環繞 return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); }
可以看到,matches()
方法實現比較簡單,其首先會判斷目標類是否是已經環繞過事務邏輯所生成的類。這里的TransactionalProxy繼承自SpringProxy,並且內部沒有任何方法,其僅僅只是起到一個標記作用,只要是使用事務代理生成的類都會實現這個接口;然后會通過getTransactionAttributeSource()
方法獲取TransactionAttributeSource對象;最后通過TransactionAttributeSource.getTransactionAttribute()
方法獲取目標方法上的事務配置,如果沒有則不對當前方法進行環繞。
這里Spring事務判斷某個方法是否需要環繞的邏輯整體上是非常簡單的,就是判斷目標方法是否配置了事務相關的屬性,比如使用@Transactional
注解的時候就是判斷目標方法上是否有該注解,並且解析該注解相關的屬性。
2. 注解解析
對於事務屬性的解析,其主要在TransactionAttributeSource.getTransactionAttribute()
方法中,這里TransactionAttributeSource只是一個接口,對於不同類型的事務聲明,其有不同的實現子類,比如我們這里使用的AnnotationTransactionAttributeSource就主要用於解析使用注解聲明的事務,如下是其getTransactionAttribute()方法的源碼:
@Override @Nullable public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // 如果當前方法是Object類中的方法,則直接返回 if (method.getDeclaringClass() == Object.class) { return null; } // 獲取當前方法緩存使用的key Object cacheKey = getCacheKey(method, targetClass); Object cached = this.attributeCache.get(cacheKey); // 從緩存中獲取當前方法解析的事務屬性,如果解析過,則將解析結果返回 if (cached != null) { if (cached == NULL_TRANSACTION_ATTRIBUTE) { return null; } else { return (TransactionAttribute) cached; } } else { // 解析當前方法的事務屬性 TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); if (txAttr == null) { // 如果當前方法上沒有事務屬性,則緩存一個表示空事務屬性的對象 this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); } else { // 獲取方法的簽名 String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); // 如果生成的事務屬性是DefaultTransactionAttribute類型的, // 則將方法簽名設置到其descriptor屬性中 if (txAttr instanceof DefaultTransactionAttribute) { ((DefaultTransactionAttribute) txAttr) .setDescriptor(methodIdentification); } if (logger.isDebugEnabled()) { logger.debug("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); } // 緩存當前方法的解析結果 this.attributeCache.put(cacheKey, txAttr); } return txAttr; } }
這里getTransactionAttribute()
方法是解析事務屬性的主干邏輯,其首先從緩存中獲取當前方法解析得到的事務屬性,如果沒有解析過則進行解析,並且緩存解析結果。可以看到,解析事務屬性的實際邏輯在computeTransactionAttribute()
方法中,如下是該方法的源碼:
@Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // 如果設置了只對public方法進行事務代理,並且當前方法不是public的,則返回null if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } Class<?> userClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null); // 獲取最為准確的方法,即如果傳入的method只是一個接口方法,則會去找其實現類的同一方法進行解析 Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass); // 如果當前方法是一個泛型方法,則會找Class文件中實際實現的方法 specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // 解析目標方法,獲取其是否存在事務屬性,如果存在則直接返回 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; }
這里對事務屬性的解析主要分為兩部分:對目標方法進行解析和對傳入方法進行解析。這兩部分的解析都分別進行了方法上的事務屬性解析和方法所在類的事務屬性解析。可以看到,將事務屬性轉換為TransactionAttribute對象的邏輯主要在findTransactionAttribute()
方法中,如下是該方法的實現邏輯(中間略去部分簡單調用):
@Nullable protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) { for (TransactionAnnotationParser annotationParser : this.annotationParsers) { TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae); if (attr != null) { return attr; } } return null; }
determineTransactionAttribute()
方法邏輯比較簡單,最終對事務屬性進行轉換的邏輯是在TransactionAnnotationParser中的,這里Spring事務使用的則是SpringTransactionAnnotationParser,如下是其parseTransactionAnnotation()方法的源碼:
@Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { // 判斷目標方法上是否存在@Transactional注解,如果不存在,則直接返回 AnnotationAttributes attributes = AnnotatedElementUtils .findMergedAnnotationAttributes(ae, Transactional.class, false, false); if (attributes != null) { // 如果目標方法上存在@Transactional注解,則獲取注解值,並且封裝為TransactionAttribute返回 return parseTransactionAnnotation(attributes); } else { return null; } } protected TransactionAttribute parseTransactionAnnotation( AnnotationAttributes attributes) { RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); // 獲取注解上的propagation值 Propagation propagation = attributes.getEnum("propagation"); rbta.setPropagationBehavior(propagation.value()); // 獲取注解上的isolation屬性值 Isolation isolation = attributes.getEnum("isolation"); rbta.setIsolationLevel(isolation.value()); // 獲取注解上的timeout屬性值 rbta.setTimeout(attributes.getNumber("timeout").intValue()); // 獲取注解上的readOnly屬性值 rbta.setReadOnly(attributes.getBoolean("readOnly")); // 獲取注解上的value屬性值 rbta.setQualifier(attributes.getString("value")); ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<>(); // 獲取注解上的rollbackFor屬性列表 Class<?>[] rbf = attributes.getClassArray("rollbackFor"); for (Class<?> rbRule : rbf) { RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); rollBackRules.add(rule); } // 獲取注解上的rollbackForClassName屬性列表 String[] rbfc = attributes.getStringArray("rollbackForClassName"); for (String rbRule : rbfc) { RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule); rollBackRules.add(rule); } // 獲取注解上的noRollbackFor屬性列表 Class<?>[] nrbf = attributes.getClassArray("noRollbackFor"); for (Class<?> rbRule : nrbf) { NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); rollBackRules.add(rule); } // 獲取注解上的noRollbackForClassName屬性列表 String[] nrbfc = attributes.getStringArray("noRollbackForClassName"); for (String rbRule : nrbfc) { NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule); rollBackRules.add(rule); } rbta.getRollbackRules().addAll(rollBackRules); return rbta; }
可以看到,對於是否需要進行事務邏輯的環繞的判斷非常簡單,就只是判斷目標方法上是否包含有@Transactional
注解,如果存在,則解析其各個屬性值,封裝為TransactionAttribute對象,然后返回。
3. 小結
本文主要講解Spring是如何判斷目標方法是否需要進行事務切面邏輯環繞的,並且講解了Spring是如何解析@Transactional
注解中各個屬性值的。可以看到,如果目標方法或其所在類標注了@Transactional
注解,則該方法就會被事務邏輯環繞。