Spring事务之切点解析详解

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> hot3.png

       在Spring事务用法示例与实现原理中我们讲到,在进行tx:annotation-driven标签解析的时候,Spring注册了三个bean:BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor和AnnotationTransactionAttributeSource。这里BeanFactoryTransactionAttributeSourceAdvisor本质上是一个Advisor,在Spring Aop中,Advisor封装了切面环绕的所有信息,最主要的就是Advice和Pointcut。这里Advice中包含了需要环绕的切面逻辑,而Pointcut中则封装了进行方法过滤的判断条件,即用于判断某个方法是否需要环绕当前切面逻辑的条件。关于这三个类的关系如下:

Spring事务之切点解析详解_第1张图片

       对应的,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 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注解,则该方法就会被事务逻辑环绕。

4. 广告

       读者朋友如果觉得本文还不错,可以点击下面的广告链接,这可以为作者带来一定的收入,从而激励作者创作更好的文章,非常感谢!

在项目开发过程中,企业会有很多的任务、需求、缺陷等需要进行管理,CORNERSTONE 提供敏捷、任务、需求、缺陷、测试管理、WIKI、共享文件和日历等功能模块,帮助企业完成团队协作和敏捷开发中的项目管理需求;更有甘特图、看板、思维导图、燃尽图等多维度视图,帮助企业全面把控项目情况。

你可能感兴趣的:(Spring事务之切点解析详解)