觉得不错请按下图操作,掘友们,哈哈哈!!!

Spring源码解析-Spring 事务

一:概述及目录

这个是源码系列的第 4 篇,感觉不错的小伙伴请一键三连哦!!!

这篇源码解析,和 Spring AOP 中的知识有许多重合的地⽅,可是⽐ AOP 要稍微简略⼀些,建议两篇⽂章对⽐学 习。

下⾯我会简略介绍⼀下 Spring 业务的基础知识,以及使⽤⽅法,然后直接对源码进⾏拆解。

目录:

Spring源码解析-Spring 事务

二. 项⽬准备

下⾯是 DB 数据和 DB 操作接⼝:

Id uname usex
1 小王
2 小李
1 小赵
@Data
public class MyUser {
    private int id;
    private String uname;
    private String usex;
}
public interface UserDao {
    // select * from user_test where id = "#{id}"
     MyUser selectUserById(Integer uid);
    // update user_test set uname =#{uname},usex = #{usex} where id = #{id}
     int updateUser(MyUser user);
}

基础测验代码,testSuccess() 是业务⽣效的状况:

@Service
public class Model {
    @Autowired
    private UserDao userDao;
    public void update(Integer id) {
        MyUser user = new MyUser();
        user.setId(id);
        user.setUname("张三-testing");
        user.setUsex("⼥");
        userDao.updateUser(user);
    }
    public MyUser query(Integer id) {
        MyUser user = userDao.selectUserById(id);
        return user;
    }
    // 正常状况
    @Transactional(rollbackFor = Exception.class)
    public void testSuccess() throws Exception {
        Integer id = 1;
        MyUser user = query(id);
        System.out.println("原记录:" + user);
        update(id);
        throw new Exception("业务⽣效");
    }
}

执⾏⼊⼝:

public class SpringMyBatisTest {
    public static void main(String[] args) throws Exception {
        String xmlPath = "applicationContext.xml";
        ApplicationContext applicationContext = new
                ClassPathXmlApplicationContext(xmlPath);
        Model uc = (Model) applicationContext.getBean("model");
        uc.testSuccess();
    }
}

输出:

Spring源码解析-Spring 事务

三:Spring 业务⼯作流程

为了⽅便⼤家能更好看懂后⾯的源码,我先全体介绍⼀下源码的执⾏流程,让⼤家有⼀个全体的知道,不然简单被 绕进去。

整个 Spring 业务源码,其实分为 2 块,我们会结合上⾯的示例,给⼤家进⾏讲解。

Spring源码解析-Spring 事务

第⼀块是后置处理,我们在创立 Model Bean 的后置处理器中,⾥⾯会做两件工作:

获取 Model 的切⾯⽅法:⾸先会拿到一切的切⾯信息,和 Model 的一切⽅法进⾏匹配,然后找到 Model 一切需 要进⾏业务处理的⽅法,匹配成功的⽅法,还需求将业务特点保存到缓存 attributeCache 中。

创立 AOP 署理目标:结合 Model 需求进⾏ AOP 的⽅法,挑选 Cglib 或 JDK,创立 AOP 署理目标。

Spring源码解析-Spring 事务

第⼆块是业务执⾏,整个逻辑⽐较复杂,我只选取 4 块最核⼼的逻辑,分别为从缓存拿到业务特点、创立并敞开事 务、执⾏业务逻辑、提交或者回滚业务

四. 源码解读

留意:Spring 的版本是 5.2.15.RELEASE,不然和我的代码不⼀样!!!

上⾯的知识都不难,下⾯才是我们的重头戏,让我们一起⾛⼀遍代码流程。

4.1 代码⼊⼝

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

这⾥需求多跑⼏次,把前⾯的 beanName 跳过去,只看 model。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

进⼊ doGetBean(),进⼊创立 Bean 的逻辑。

Spring源码解析-Spring 事务

进⼊ createBean(),调⽤ doCreateBean()。

Spring源码解析-Spring 事务

进⼊ doCreateBean(),调⽤ initializeBean()。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

假如看过我前⾯⼏期系列源码的同学,对这个⼊⼝应该会⾮常熟悉,其实便是⽤来创立署理目标。

4.2 创立署理目标

Spring源码解析-Spring 事务

这⾥是要点!敲⿊板!!!

    1. 先获取 model 类的一切切⾯列表;
    1. 创立⼀个 AOP 的署理目标。

Spring源码解析-Spring 事务

4.2.1 获取切⾯列表

Spring源码解析-Spring 事务

这⾥有 2 个重要的⽅法,先执⾏ findCandidateAdvisors(),待会我们还会再回来 findEligibleAdvisors()。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

顺次回来,重新来到 findEligibleAdvisors()。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务
进⼊ canApply(),开端匹配 model 的切⾯。

Spring源码解析-Spring 事务

这⾥是要点!敲⿊板!!! 这⾥只会匹配到 Model.testSuccess() ⽅法,我们直接进⼊匹配逻辑。

Spring源码解析-Spring 事务

假如匹配成功,还会把业务的特点配置信息放⼊ attributeCache 缓存。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

我们顺次回来到 getTransactionAttribute(),再看看放⼊缓存中的数据。

Spring源码解析-Spring 事务

再回到该⼩节开头,我们拿到 mdoel 的切⾯信息,去创立 AOP 署理目标。

Spring源码解析-Spring 事务

4.2.2 创立 AOP 署理目标

创立 AOP 署理目标的逻辑,在上⼀篇⽂章【Spring源码解析-Spring AOP】讲解过,我是经过 Cglib 创立,感兴趣的同学能够翻⼀下我的历史⽂章。

4.3 业务执⾏

回到业务逻辑,经过 model 的 AOP 署理目标,开端执⾏主⽅法。

Spring源码解析-Spring 事务

因为署理目标是 Cglib ⽅式创立,所以经过 Cglib 来执⾏。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

这⾥是要点!敲⿊板!!!

下⾯的代码是业务执⾏的核⼼逻辑 invokeWithinTransaction()。

Spring源码解析-Spring 事务

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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
    //从tx特点目标中获取出标示了@Transactionl的⽅法描述符
    final String joinpointIdentification = methodIdentification(method,
            targetClass, txAttr);
    //处理声明式业务
    if (txAttr == null || !(tm instanceof
            CallbackPreferringPlatformTransactionManager)) {
        //有没有必要创立业务
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr,
                joinpointIdentification);
        Object retVal;
        try {
            //调⽤钩⼦函数进⾏回调⽬标⽅法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            //抛出反常进⾏回滚处理
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            //清空我们的线程变量中transactionInfo的值
            cleanupTransactionInfo(txInfo);
        }
        //提交业务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    //编程式业务
    else {
        // 这⾥不是我们的要点,省略...
    }
}

4.3.1 获取业务特点

在 invokeWithinTransaction() 中,我们找到获取业务特点的⼊⼝。

Spring源码解析-Spring 事务

从 attributeCache 获取业务的缓存数据,缓存数据是在 “3.2.1 获取切⾯列表” 中保存的。

Spring源码解析-Spring 事务

4.3.2 创立业务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

经过 doGetTransaction() 获取业务。

protected Object doGetTransaction() {
    //创立⼀个数据源业务目标
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    //是否允许当时业务设置坚持点
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    /**
     * TransactionSynchronizationManager 业务同步管理器目标(该类中都是部分线程变量)
     * ⽤来保存当时业务的信息,我们第⼀次从这⾥去线程变量中获取 业务衔接持有器目标 经过数据源为key
     去获取
     * 因为第⼀次进来开端业务 我们的业务同步管理器中没有被存放.所以此刻获取出来的conHolder为null
     */
    ConnectionHolder conHolder =
            (ConnectionHolder)
                    TransactionSynchronizationManager.getResource(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    //回来业务目标
    return txObject;
}

经过 startTransaction() 敞开业务。

Spring源码解析-Spring 事务

下⾯是敞开业务的详细逻辑,了解⼀下即可。

protected void doBegin(Object transaction, TransactionDefinition definition) {
    //强制转化业务目标
    DataSourceTransactionObject txObject = (DataSourceTransactionObject)
            transaction;
    Connection con = null;
    try {
        //判别业务目标没有数据库衔接持有器
        if (!txObject.hasConnectionHolder() ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            //经过数据源获取⼀个数据库衔接目标
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC
                        transaction");
            }
            //把我们的数据库衔接包装成⼀个ConnectionHolder目标 然后设置到我们的txObject目标
            中去
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
        //符号当时的衔接是⼀个同步业务
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        //为当时的业务设置隔离等级
        Integer previousIsolationLevel =
                DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        最终回来到 invokeWithinTransaction(),得到 txInfo 目标。
        //关闭⾃动提交
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual
                        commit");
            }
            con.setAutoCommit(false);
        }
        //判别业务为只读业务
        prepareTransactionalConnection(con, definition);
        //设置业务激活
        txObject.getConnectionHolder().setTransactionActive(true);
        //设置业务超时时刻
        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }
        // 绑定我们的数据源和衔接到我们的同步管理器上 把数据源作为key,数据库衔接作为value 设
        置到线程变量中
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(obtainDataSource(),
                    txObject.getConnectionHolder());
        }
    }
    catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            //释放数据库衔接
            DataSourceUtils.releaseConnection(con, obtainDataSource());
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection
    }
}

最终回来到 invokeWithinTransaction(),得到 txInfo 目标。

Spring源码解析-Spring 事务

4.3.3 执⾏逻辑

仍是在 invokeWithinTransaction() 中,开端执⾏业务逻辑。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

进⼊到真实的业务逻辑。

Spring源码解析-Spring 事务

执⾏结束后抛出反常,顺次回来,⾛后续的回滚业务逻辑。

4.3.4 回滚业务

仍是在 invokeWithinTransaction() 中,进⼊回滚业务的逻辑。

Spring源码解析-Spring 事务

执⾏回滚逻辑很简略,我们只看怎么判别是否回滚。

Spring源码解析-Spring 事务

Spring源码解析-Spring 事务

假如抛出的反常类型,和业务界说的反常类型匹配,证明该反常需求捕获。

之所以⽤递归,不仅需求判别抛出反常的自身,还需求判别它承继的⽗类反常,满⾜恣意⼀个即可捕获。

Spring源码解析-Spring 事务

到这⾥,一切的流程结束。

五. 总要有总结

我们再⼩节⼀下,⽂章先介绍了业务的使⽤示例,以及业务的执⾏流程。

之后再剖析了业务的源码,分为 2 块:

  • 先匹配出 model 目标一切关于业务的切⾯列表,并将匹配成功的业务特点保存到缓存;
  • 从缓存取出业务特点,然后创立、发动业务,执⾏业务逻辑,最终提交或者回滚业务。

这篇⽂章,是 Spring 源码解析的第 4 篇,假如之前已经看过 AOP 的源码解析,这篇理解起来就简单许多,可是假如上来 就直接肝,可能会有一丢丢难度哦。

因为博主现在没有对应大众号,有些内容借鉴一个老哥 ,他大众号 大众号:楼仔,能够关注下,我们下期再见!

本文正在参加「金石方案」