电子商务网站建设的参考文献,电商眼,甜妹妹福利wordpress,网站设计班培训今天的任务是剖析源码#xff0c;看看Spring 是怎么运行事务的#xff0c;并且是基于当前最流行的SpringBoot。还有#xff0c;我们之前剖析Mybatis 的时候#xff0c;也知道#xff0c;Mybatis 也有事务#xff0c;那么#xff0c;他俩融合之后#xff0c;事务是交给谁…今天的任务是剖析源码看看Spring 是怎么运行事务的并且是基于当前最流行的SpringBoot。还有我们之前剖析Mybatis 的时候也知道Mybatis 也有事务那么他俩融合之后事务是交给谁的又是怎么切换的今天这几个问题我们都要从源码中找到答案。
1. Spring 的事务如何运行 如果各位使用过SpringBoot 那么就一定知道如何在Spring中使用注解比如在一个类或者一个方法上使用 Transactional 注解在一个配置类上加入一个 EnableTransactionManagement 注解代表启动事务。而这个配置类需要实现 TransactionManagementConfigurer 事务管理器配置接口。并实现 annotationDrivenTransactionManager 方法返回一个包含了 配置好数据源的 DataSourceTransactionManager 事务对象。这样就完成了事务配置就可以在Spring使用事务的回滚或者提交功能了。 这个 saveList 方法就在Spring事务的控制之下如果发生了异常就会回滚事务。如果各位知道更多的Spring的事务特性可以在注解中配置比如什么异常才能回滚比如超时时间比如隔离级别比如事务的传播。就更有利于理解今天的文章了。
我们基于一个 Junit 测试用例来看看Spring的事务时如何运行的。 在测试用例中执行该方法参数时一个空的List这个Sql的运行肯定是失败的。我们主要看看他的运行过程。我们讲断点打在该方法上。断点进入该方法。 注意dataCollectionShareService 对象已经被 Cglib 代理了那么他肯定会走 DynamicAdvisedInterceptor 的 intercept 方法我们断点进入该方法查看这个方法我们已经很属性了该方法中最重要的事情就是执行通知器或者拦截器的方法那么该代理有通知器吗 有一个通知器。是什么呢 一个事务拦截器也就是说如果通知器链不为空就会依次执行通知器链的方法。那么 TransactionInterceptor 到底是什么呢 该类实现了通知器接口也实现类 MethodInterceptor 接口并实现了该接口的 invoke 方法在 DynamicAdvisedInterceptor 的 intercept 方法中最终会调用每个 MethodInterceptor 的 invoke 方法那么TransactionInterceptor 的 invoke 方法是如何实现的呢 invoke 方法中会调用自身的 invokeWithinTransaction 方法看名字该方法和事务相关。该方法参数是由目标方法目标类一个回调对象构成。 那么我们就进入该方法查看该方法很长
/*** General delegate for around-advice-based subclasses, delegating to several other template* methods on this class. Able to handle {link CallbackPreferringPlatformTransactionManager}* as well as regular {link PlatformTransactionManager} implementations.* param method the Method being invoked* param targetClass the target class that were invoking the method on* param invocation the callback to use for proceeding with the target invocation* return the return value of the method, if any* throws Throwable propagated from the target invocation*/protected Object invokeWithinTransaction(Method method, Class? targetClass, final InvocationCallback invocation)throws Throwable {// If the transaction attribute is null, the method is non-transactional.final TransactionAttribute txAttr getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm determineTransactionManager(txAttr);final String joinpointIdentification methodIdentification(method, targetClass, txAttr);if (txAttr null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);return retVal;}else {// Its a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {Object result ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,new TransactionCallbackObject() {Overridepublic Object doInTransaction(TransactionStatus status) {TransactionInfo txInfo prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);try {return invocation.proceedWithInvocation();}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.return new ThrowableHolder(ex);}}finally {cleanupTransactionInfo(txInfo);}}});// Check result: It might indicate a Throwable to rethrow.if (result instanceof ThrowableHolder) {throw ((ThrowableHolder) result).getThrowable();}else {return result;}}catch (ThrowableHolderException ex) {throw ex.getCause();}}}该方法主要逻辑
获取事务属性根据事务属性获取事务管理器判断属性是否空或者事务管理器是否不是 CallbackPreferringPlatformTransactionManager 类型如果是该类型则会执行事务管理器的 execute 方法。生成一个封装了事务管理器事务属性方法签名字符串事务状态对象 的 TransactionInfo 事务信息对象。该对象会在事务回滚或者失败时起作用。调用目标对象方法或者是下一个过滤器的方法。如果方法由异常则执行 completeTransactionAfterThrowing 方法调用事务管理器的回滚方法。如果没有异常调用 commitTransactionAfterReturning 提交方法。最后返回返回值。
可以说该方法就是Spring 事务的核心调用根据目标方法是否有异常进行事务的回滚。
那么我们需要一行一行的看看该方法实现。
首先看事务的属性。
2. TransactionAttribute 事务属性 invokeWithinTransaction 方法中调用了 自身的 getTransactionAttributeSource 方法返回一个TransactionAttributeSource 对象并调用该对象的 getTransactionAttribute 方法参数是目标方法和目标类对象。首先看 getTransactionAttributeSource 方法该方法直接返回了抽象类 TransactionAspectSupport 中定义的 TransactionAttributeSource 属性。该属性的是什么时候生成的我们稍后再说。我们debug 后返回的是 TransactionAttributeSource 接口的实现类 AnnotationTransactionAttributeSource 看名字注解事务属性资源名字起的好很重要啊。我们进入该类查看。 这是该类的继承机构图。我们重点还是关注该类的 getTransactionAttribute 方法该方法有抽象类 AbstractFallbackTransactionAttributeSource 也就是 AnnotationTransactionAttributeSource 的父类完成。我们看看该方法。 该方法大部分都是缓存判断最重要的一行代码楼主已红框标出。computeTransactionAttribute 方法计算事务属性。进入该方法查看 该方法是返回事务属性的核心方法首先根据 class 和 method 对象生成一个完整的method 对象然后调用 findTransactionAttribute 方法参数就是该 method 对象findTransactionAttribute 方法是抽象方法由子类实现可见 computeTransactionAttribute 是个模板方法模式。那么我们就看看他的子类 AnnotationTransactionAttributeSource 是如何实现的。该方法调用了自身的 determineTransactionAttribute 方法。该方法实现入下 该方法会判断该 Method 对象是否含有注解。并循环 AnnotationTransactionAttributeSource 对象的 annotationParsers 注解解析器集合对该方法进行解析。如果解析成功则返回该注解元素。我想我们也已经猜到了这个注解解析器解析的就是 Transactional 注解。
3. Transactional 注解解析器 SpringTransactionAnnotationParser
我们说AnnotationTransactionAttributeSource 对象中又多个解析器。那么这些解析器是什么时候生成的呢构造方法中生成的。 该构造方法由一个布尔属性然后创建一个链表也创建一个 SpringTransactionAnnotationParser 对象添加进链表中。这样就完成了解析器的创建。构造方法什么时候调用的呢我们稍后再讲。
我们看看注解解析器是怎么解析方法对象的。 首先根据指定的 Transactional 注解和给定的方法调用工具方法 getMergedAnnotationAttributes 获取方法上的注解属性。然后调用重载方法 parseTransactionAnnotation 。 可以看到该方法首先创建了一个 RuleBasedTransactionAttribute 对象然后一个个解析注解中的元素并将这些元素设置到 RuleBasedTransactionAttribute 对象中注意其中有个 RollbackRuleAttribute 的集合存储着该注解属性的回滚相关的属性。最后添加到 RuleBasedTransactionAttribute 的RollbackRules 集合中。
到这里就完成了解析器的解析。返回了一个 RuleBasedTransactionAttribute 对象。
回到 拦截器的 invokeWithinTransaction 方法中此时已经获取了 属性对象。根据方法也就是说如果返回值是null说明该方法没有事务注解在 getTransactionAttribute 方法中也会将该方法作为key NULL_TRANSACTION_ATTRIBUTE 作为 value放入缓存如果不为null那么就将 TransactionAttribute 作为 value 放入缓存。
有了事务属性再获取事务管理器。也就是 determineTransactionManager 方法。
4. 事务管理器。 我们注意到调用了自身的 determineTransactionManager 方法返回了一个 PlatformTransactionManager 事务管理器。这个事务管理器就是我们在我们的配置类中写的 那么这个事务管理器是什么呢事务管理器就是真正执行事务回滚或提交的执行单位我们看看该类
继承图 结构图 红框标注的方法就是执行正在事务逻辑的方法其中又封装了数据源也就是 JDBC 的 Connection 。比如 doCommit 方法 我们看看determineTransactionManager 是如何获取事务管理器的。 该方法步骤入下
如果事务属性为null 或者 容器工厂为null则返会自身的 transactionManager 事务管理器。如果都不为null则获取事务属性的限定符号根据限定符从容器中获取 事务管理器。如果没有限定符则根据事务管理器的BeanName从容器中获取。如果都没有则获取自身的事务管理器如果自身还没有则从缓存中取出默认的。如果默认的还没有则从容器中获取PlatformTransactionManager 类型的事务管理器最后返回。
这里重点是自身的事务管理器从何而来我们先按下不表。
到这里我们已经有了事务管理器。就需要执行 invokeWithinTransaction 下面的逻辑了。回到 invokeWithinTransaction 方法我们的返回值肯定满足第一个if 条件因为我们的事务管理器不是 CallbackPreferringPlatformTransactionManager 类型的。进入if 块。 首先创建一个事务信息对象。该类是什么呢
属性 构造方法 该类包含了一个 事务管理器事务属性事务方法字符串。
接着执行回调类InvocationCallback 的 proceedWithInvocation 方法该方法会执行下一个通知器的拦截方法如果有的话最后执行目标方法这里目标方法被 try 住了如果发生异常则执行completeTransactionAfterThrowing 方法并抛出异常在 finally 块中执行清理工作。如果成功执行则执行 commitTransactionAfterReturning 方法。最后返回目标方法返回值。
我们重点看看 completeTransactionAfterThrowing 方法和 commitTransactionAfterReturning 方法。
5. TransactionInterceptor 的 completeTransactionAfterThrowing 方法事务如何回滚。 该方法主要内容在红框中首先判断该事务对象是否和该异常匹配如果匹配则回滚否则则提交。那么是否匹配的逻辑是怎么样的呢我们的事务属性是什么类型的RuleBasedTransactionAttribute 就是我们刚刚创建解析注解后创建的。那么我就看看该类的 rollbackOn 方法 首先循环解析注解时添加进集合的回滚元素。并递归调用RollbackRuleAttribute 的 getDepth 方法如果这个异常的名字和注解中的异常名字匹配则返回该异常的回滚类型。最后判断如果没有匹配到则调用父类的 rollbackOn 方法如果匹配到了并且该属性类型不是 NoRollbackRuleAttribute 类型返回true。表示匹配到了可以回滚。那么父类的 rollbackOn 方法肯定就是默认的回滚方法了。
这是父类的 rollbackOn 方法 该方法判断该异常如果是 RuntimeException 类型异常或者 是 Error 类型的就回滚。这就是默认的回滚策略。 我们的方法肯定是匹配的 RuntimeException 异常就会执行下面的方法。 可以看到这行代码就是执行了我们的事务管理器的 rollback 方法并且携带了事务状态对象。该方法实现在抽象类 AbstractPlatformTransactionManager 中调用了自身的 processRollback 方法做真正的实现。 该方法首先切换事务状态其实就是关闭SqlSession。 然后调用 doRollback 方法。
首先从状态对象中获取数据库连接持有对象然后获取数据库连接调用 Connection 的 rollback 方法也就是我们学习JDBC 时使用的方法。最后修改事务的状态。
到这里事务的回滚就结束了。
那么事务时如何提交的呢
6. TransactionInterceptor 的 commitTransactionAfterReturning 方法事务如何提交。
该方法简单的调用了事务管理器的 commit 方法。
AbstractPlatformTransactionManager 的 commit 方法。 首先判断了事务的状态如果状态不匹配则调用回滚方法。如果状态正常执行 processCommit 方法。该方法很长楼主只截取其中一段
首先commit 之前做一些状态切换工作。最重要的是执行 doCommit 方法如果异常了则回滚。那么 DataSourceTransactionManager 的 doCommit 是如何执行的呢
可以看到底层也是调用 JDBC 的 Connection 的 commit 方法。
到这里我们就完成了数据库的提交。
7. 事务运行之前做了哪些工作 从前面的分析我们已经知道了事务是如何运行的如何回滚的又是如何提交的。在这是交互型的框架里事务系统肯定做了很多的准备工作同时我们留下了很多的疑问比如事务管理器从何而来 TransactionAttributeSource 属性何时生成AnnotationTransactionAttributeSource 构造什么时候调用
我们一个个的来解释。
在Spring 中有一个现成的类ProxyTransactionManagementConfiguration我们看看该类
看到这个类应该可以解开我们的疑惑这个类标注了配置注解会在IOC的时候实例化该类而该类中产生了几个Bean比如事务拦截器 TransactionInterceptor创建了 AnnotationTransactionAttributeSource 对象并向事务拦截器添加了事务管理器。最后将事务拦截器封装成通知器。那么剩下最后一个问题就是事务管理器从何而来答案是他的父类 AbstractTransactionManagementConfiguration 该类也是个配置类自动注入了 TransactionManagementConfigurer 的配置集合而并且寻找了配置 EnableTransactionManagement 注解的类而我们在我们的项目中就是按照这个标准来实现的
我们关联这两个类就能一目了然Spring在启动的时候会加载这两个配置类在对 AbstractTransactionManagementConfiguration 的 setConfigurers 方法进行注入的时候会从容器中找到对应类型的配置并调用配置类的 annotationDrivenTransactionManager 方法也就是我们实现的方法获取到我们创建的 DataSourceTransactionManager 类。这样我们的事务拦截器相关的类就完成了在Spring中的依赖关系。
但是这个时候Spring中的事务运行还没有搭建完成。比如什么时候创建类的代理根据什么创建代理因为我们知道Spring 中的事务就是使用AOP来完成的必须使用动态代理或者 Cglib 代理来对目标方法进行拦截。
这就要复习我们之前的Spring IOC 的启动过程了。Spring 在创建bean的时候会对每个Bean 的所有方法进行遍历如果该方法匹配系统中任何一个拦截器的切点就创建一个该Bean的代理对象。并且会将对应的通知器放入到代理类中。以便在执行代理方法的时候进行拦截。
具体代码步骤楼主贴一下
在对bean 进行初始化的时候会执行 AutowireCapableBeanFactory 接口的 applyBeanPostProcessorsAfterInitialization 的方法其中会遍历容器中所有的bean后置处理器后置处理器会调用 postProcessAfterInitialization 方法对bean进行处理。
在处理过程中对bean 进行包装也就是代理的创建调用 getAdvicesAndAdvisorsForBean 方法该方法会根据bean的信息获取到对应的拦截器并创建代理创建代理的过程我们之前已经分析过了不再赘述。
寻找匹配拦截器过程首先找到所有的拦截器然后根据bean的信息进行匹配。
匹配的过程就是找到目标类的所有方法遍历并调用拦截器的方法匹配器对每个方法进行匹配。方法匹配器就是事务拦截器中的 BeanFactoryTransactionAttributeSourceAdvisor 类该类封装了 AnnotationTransactionAttributeSource 用于匹配事务注解的匹配器。
最终调用方法匹配器中封装的注解解析器解析方法判断方法是否含有事务注解从而决定是否生成代理
到这里就完成了所有事务代理对象的创建。
项目中的每个Bean都有了代理对象在执行目标方法的时候代理类会查看目标方法是否匹配代理中拦截器的方法匹配器中定义的切点。如果匹配则执行拦截器的拦截方法否则直接执行目标方法。这就是含有事务注解和不含有事务注解方法的执行区别。
到这里我们还剩下最后一个问题我们知道在分析mybatis 的时候mybatis 也有自己的事务管理器那么他们融合之后他们的事务管理权在谁的手上又是根据什么切换的呢
8. mybatis 和 Spring 的事务管理权力之争
我们之前说过在Spring中mybatis 有 SqlSessionTemplate 代理执行其实现类动态代理的 InvocationHandler 方法那么最重要的方法就是 invoke 方法其实这个方法我们已经看过了今天再看一遍
我们今天重点关注是否提交报错肯定回滚其中红框标出来的 if 判断就是判断这个事务到底是Spring 来提交还是 mybatis 来提交那么我们看看这个方法 isSqlSessionTransactional 该方法从Spring 的容器中取出持有 SqlSession 的 持有类判断Spirng 持有的 SqlSession 和 Mybatis 持有的是否是同一个如果是则交给Spring否则Mybatis 自己处理。可以说很合理。
总结
今天的这篇文章可以说非常的长我们分析了 SpringBoot 的事务运行过程事务环境的搭建过程mybatis 的事务和 Spring 事务如何协作。知道了整个事务其实是建立在AOP的基础之上其核心类就是 TransactionInterceptor该类就是 invokeWithinTransaction 方法是就事务处理的核心方法其中封装了我们创建的 DataSourceTransactionManager 对象该对象就是执行回滚或者提交的执行单位 其实TransactionInterceptor 和我们平时标注 Aspect 注解的类的作用相同就是拦截指定的方法而在 TransactionInterceptor 中是通过是否标有事务注解来决定的。如果一个类中任意方法含有事务注解那么这个方法就会被代理。而Mybatis 的事务和Spring 的事务协作则根据他们的SqlSession 是否是同一个SqlSession 来决定的如果是同一个则交给Spring如果不是Mybatis 则自己处理。
通过阅读源码我们已经弄清楚了SpirngBoot 整个事务的运行过程。实际上Spring 的其他版本也大同小异。底层都是 TransactionInterceptor 只不过入口不一样。我相信在以后的工作中如果遇到了Spring事务相关的问题再也不会感到无助了因为知道了原理可以深入到源码中查看。 原文spring 事务源码分析_spring事物源码分析_洋洋yang羊的博客-CSDN博客