文档章节

Spring事务之切点解析详解

爱宝贝丶
 爱宝贝丶
发布于 2018/09/05 10:31
字数 2273
阅读 835
收藏 14

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

Advisor

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

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 341
博文 136
码字总数 456051
作品 0
武汉
程序员
私信 提问
Spring应用学习——AOP

AOP 1. AOP:即面向切面编程,采用横向抽取机制,取代了传统的继承体系的重复代码问题,如下图所示,性能监控、日志记录等代码围绕业务逻辑代码,而这部分代码是一个高度重复的代码,也就是在...

江左煤郎
2018/11/18
38
0
《Spring5学习》04 - 面向切面编程

一、Spring面向切面编程的基本概念 面向切面编程(即AOP):把项目中需要再多处使用的功能比如日志、安全和事务等集中到一个类中处理,而不用在每个需要用到该功能的地方显式调用。 横切关注...

老韭菜
2018/08/19
110
0
Spring AOP 功能使用详解

相关文章 Spring 中 bean 注册的源码解析 Spring bean 创建过程源码解析 Spring 的 getBean 方法源码解析 前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可...

TSMYK
2018/12/25
308
2
Spring编程式和声明式事务实例讲解

Java面试通关手册(Java学习指南):https://github.com/Snailclimb/JavaGuide 历史回顾: 可能是最漂亮的Spring事务管理详解 Spring事务管理 Spring支持两种方式的事务管理: 编程式事务管理...

snailclimb
2018/05/23
0
0
Spring之旅第五篇-AOP详解

一、什么是AOP? Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实...

花漾年华
05/08
9
0

没有更多内容

加载失败,请刷新页面

加载更多

javaagent使用demo详解

javaagent又称java探针,结合javassist或asm等框架对字节码文件进行操作,从而更优雅的实现“AOP”等功能,减少对原代码的侵入性等。从而我们可以借此来实现微服务等的全链路追踪以及项目环境...

xiaomin0322
24分钟前
3
0
jar包是怎么提交到Spark上运行的

我们都知道,写好spark程序后,可以通过命令行spark-submit方式提交到集群,那么这个具体的过程是怎么搞得呢? spark有多种集群方式,如yarn,standalone等。提交方式又分为client和cluster...

守望者之父
38分钟前
5
0
最好的重试是指数后退和抖动

1. 概述 在本教程中,我们将探讨如何使用两种不同的策略改进客户端重试:指数后退和抖动。 2. 重试 在分布式系统中,多个组件之间的网络通信随时可能发生故障。 客户端应用程序通过实现重试来...

liululee
49分钟前
5
0
聊一聊大厂内部的安全管理机制

工作了两个月了体会到了很多之前做外包小项目没有的东西,不得不说大厂的还是有自己一套的完善的体制,不会像B站那样泄露自己整个后台的源码这种事情发生。 电脑办公 比如说在使用电脑办公这...

gzc426
今天
6
0
如何利用deeplearning4j中datavec对图像进行处理

NativeImageLoader Labelloader = new NativeImageLoader(112, 112, 3,new FlipImageTransform(-1)); 一、导读 众所周知图像是有红绿蓝三种颜色堆叠而成,利用deeplearning对图像处理,必须把...

冷血狂魔
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部