Spring事務之切點解析詳解

       在Spring事務用法示例與實現原理中咱們講到,在進行tx:annotation-driven標籤解析的時候,Spring註冊了三個bean:BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor和AnnotationTransactionAttributeSource。這裏BeanFactoryTransactionAttributeSourceAdvisor本質上是一個Advisor,在Spring Aop中,Advisor封裝了切面環繞的全部信息,最主要的就是Advice和Pointcut。這裏Advice中包含了須要環繞的切面邏輯,而Pointcut中則封裝了進行方法過濾的判斷條件,即用於判斷某個方法是否須要環繞當前切面邏輯的條件。關於這三個類的關係以下:java

Advisor

       對應的,Spring事務中聲明的這三個bean就與切面環繞所使用的組織結構徹底一致,這裏TransactionInterceptor實現了Advice接口,進行事務切面環繞的邏輯也封裝在了這個bean中;AnnotationTransactionAttributeSource則封裝了目標方法是否須要進行事務邏輯環繞的判斷邏輯,實際上,其沒有實現Pointcut接口,可是BeanFactoryTransactionAttributeSourceAdvisor在進行目標方法判斷的時候實際上仍是委託給了AnnotationTransactionAttributeSource進行。對於這幾個類的講解咱們會依次進行,本文則主要講解AnnotationTransactionAttributeSource是如何判斷目標方法是否須要進行事務邏輯環繞的。緩存

1. 切點聲明

       在BeanFactoryTransactionAttributeSourceAdvisor中,其聲明瞭一個TransactionAttributeSourcePointcut類型的屬性,而且實現了其getTransactionAttributeSource()方法,這個方法的返回值是一個TransactionAttributeSource類型的對象,而實際上,其返回的就是AnnotationTransactionAttributeSource。這裏建立Pointcut的源碼以下:ide

@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()中,以下是該方法的源碼:this

@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()方法獲取目標方法上的事務配置,若是沒有則不對當前方法進行環繞。.net

       這裏Spring事務判斷某個方法是否須要環繞的邏輯總體上是很是簡單的,就是判斷目標方法是否配置了事務相關的屬性,好比使用@Transactional註解的時候就是判斷目標方法上是否有該註解,而且解析該註解相關的屬性。debug

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()方法中,以下是該方法的源碼:code

@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()方法的源碼:blog

@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註解,則該方法就會被事務邏輯環繞。

相關文章
相關標籤/搜索