Java教程

spring源码分析 事务

本文主要是介绍spring源码分析 事务,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

JDBC方式下的事务使用示例

事务自定义标签

注册InfrastructureAdvisorAutoProxy-Creator

获取对应class/method的增强器

寻找候选增强器

候选增强器中寻找到匹配项

提取事务标签

事务增强器

创建事务

获取事务

处理已经存在的事务

准备事务信息

回滚处理

回滚条件

回滚处理

回滚后的信息清除

事务提交


注意:本文摘自 spring源码深度解析

Spring声明式事务让我们从复杂的事务处理中得到解脱,使我们再也不需要去处理获得连接、 关闭连接、事务提交和回滚等操作,再也不需要在与事务相关的方法中处理大量的 try ... catch ... finally代码。Spring中事务的使用虽然已经相对简单得多,但是,还是有很多的使用及配置规则,有兴趣的读者可以自己查阅相关资料进行深入研究,这里只列举出最常用的使用方法。

同样,我们还是以最简单的示例来进行直观地介绍。

JDBC方式下的事务使用示例

在上面的测试示例中,UserServicelmpl类对接口UserService中的save函数的实现最后加入了一句抛出异常的代码: throw new RuntimeExceµtion("aa")。当注掉这段代码执行测试类,那么会看到数据被成功的保存到了数据库中,但是如果加入这段代码时再次运行测试类,发现 此处的操作并不会将数据保存到数据库中。

汪意 默认情况下Spring中的事务处理只对RuntimeException方法进行回滚,所以,如果此处将Runtime Exception替换成普通的Exception不会产生回滚效果。

事务自定义标签

对于Spring中事务功能的代码分析,我们首先从配置文件开始入手,在配置文件中有这样 一个配置: <tx:annotation-driven />。可以说此处配翌是事务的开关,如果没有此处配置,那么 Spring中将不存在事务的功能。那么我们就从这个配置开始分析。

根据之前的分析,我们因此可以判断,在自定义标签中的解析过程中一定是做了一些辅助操作,于是我们先从自定义标签入手进行分析。

使用Eclipse搜索全局代码,关键字annotation-drive, 最终锁定类TxNamespaceHandler, 在TxNamespaceHandler中的init方法中:

根据自定义标签的使用规则以及上面的代码,可以知道,在遇到诸如-<tx:annotation-driven为 开头的配置后,Spring都会使用AnnotationDrivenBeanDefinitionParser类的parse方法进行解析。

在解析中存在对于mode属性的判断,根据代码,如果我们需要使用AspectJ的方式进行事务切入(Spring中的事务是以AOP为基础的),那么可以使用这样的配置:

注册InfrastructureAdvisorAutoProxy-Creator

我们以默认配置为例子进行分析,进入AopAutoProxyConfigurer类的configureAutoProxyCreator:

上面的代码注册了代理类及3个bean, 很多读者会直接略过,认为只是注册3个bean而己,确实,这里只注册了3个bean, 但是这3个bean支撑了整个的事务功能,那么这3个bean 是怎么组织起来的呢?

首先,其中的两个bean被注册到了一个名为advisorDef的bean中,advisorDef使用 BeanactoryTransactionAttributeSourceAdvisor作为其class属性。也就是说BeanFactoryTransaction AttributeSourceAdvisor代表着当前bean, 如图所示,具体代码如下:

那么如此组装的目的是什么呢?我们暂且留下一个悬念,接着分析代码。上面函数 configureAutoProxyCreator中的第一句貌似很简单但却是很重要的代码:

对于解析来的代码流程AOP中已经有所分析,上面的两个函数主要目的是注册了 InfrastructureAdvisorAutoProxyCreator类型的bean, 那么注册这个类的目的是什么呢?查看这 个类的层次,如图所示。

从上面的层次结构中可以看到,lnfrastructureAdvisorAutoProxyCreator间接实现了 SmartlnstantiationAwareBeanPostProcessor, 而SmartlnstantiationAwareBeanPostProcessor又继承 自InstantiationAwareBeanPostProcessor, 也就是说在Spring中,所有bean实例化时Spring都会 保证调用其rostProcessAfterInitial ization方法,其实现是在父类AbstractAutoProxyCreator类中实现。

以之前的示例为例,当实例化userService的bean时便会调用此方法,方法如下:

这里实现的主要目的是对指定Bean进行封装,当然首先要确定是否需要封装,检测及封装的工作都委托给了wraplfNecessary函数进行。

wrapIfNecessary函数功能实现起来很复杂,但是逻辑上理解起来还是相对简单的,在 wrapIfNecessary函数中主要的工作如下。

找出指定bean对应的增强器。 根据找出的增强器创建代理。

听起来似乎简单的逻辑,Spring;中又做了哪些复杂的工作呢?对于创建代理的部分,通过之前的分析相信大家已经很熟悉了,但是对于增强器的获取,Spring 又是怎么做的呢?

获取对应class/method的增强器

获取指定bean对应的增强器,其中包含两个关键字:增强器与对应。也就是说在 getAdvicesAndAdvisorsForBean函数中,不但要找出增强器,而且还需要判断增强器是否满足要求。

其实我们也渐渐地体会到了Spring中代码的优秀,即使是一个很复杂的逻辑,在Spring 中也会被拆分成若干个小的逻辑,然后在每个函数中实现,使得每个函数的逻辑简单到我们能快速地理解,而不会像有些人开发的那样,将一大堆的逻辑都罗列在一个函数中,给后期维护人员造成巨大的困扰。

同样,通过上面的函数,Spring又将任务进行了拆分,分成了获取所有增强器与增强器是 否匹配两个功能点。

寻找候选增强器

在findCandidateAdvisors函数中完成的就是获取增强器的功能。

对于上面的函数,你看懂其中的奥妙了吗?首先是通过BeanfactoryUtils类提供的工具方 法获取所有对应Advisor.class的类,获取办法无非是使用ListableBeanFactory中提供的方法:

String[] getBeanNamesForType(Class<?> type, boolean inclucteNonSingletons, boolean allowEagerInit);

而当我们知道增强器在容器中的 BeanName时,获取增强器已经不是问题了,在BeanFactory 中提供了这样的方法,可以帮助我们快速定位对应的bean实例。

<T>T getBean(String name, Class<T> requireTrype) throws BeansException;

或许你已经忘了之前留下的悬念,在我们讲解自定义标签时曾经注册了一个类型为 BeanFactoryTransactionAttributeSourceAdvisor的bean, 而在此bean中我们又注入了另外两个 Bean, 那么此时这个Bean就会被开始使用了。因为BeanFactoryTransactionAttribute Source Advisor同样也实现了Advisor接口,那么在获取所有增强器时自然也会将此bean提取出来,并随着其他增强器一起在后续的步骤中被织入代理。

候选增强器中寻找到匹配项

当找出对应的增强器后,接来的任务就是看这些增强器是否与对应的class匹配了,当然不只是class, class内部的方法如果匹配也可以通过验证。

当前我们分析的是对于UserService是否适用于此增强方法,那么当前的advisor就是之前查找出来的类型为BeanFactoryTransactionAttributeSourceAdvisor的bean实例,而通过类的层次结构我们又知道: BeanFactoryTransactionAttributeSourceAdvisor间接实现了PointcutAdvisor。因此,在canApply函数中的第二个if判断时就会通过判断,会将BeanFactoryTransaction AttributeSourceAdvisor中的getPointcut()方法返回值作为参数继续调用canApply方法,而 getPoint()方法返回的是TransactionAttributeSourcePointcut类型的实例。对于transactionAttributeSource这个属性大家还有印象吗?这是在解析自定义标签时注入进去的。

通过上面函数大致可以理清大体脉络,首先获取对应类的所有接口并连同类本身一起遍历,遍历过程中又对类中的方法再次遍历,一旦匹配成功便认为这个类适用于当前增强器。

到这里我们不禁会有疑问,对于事务的配置不仅仅局限于在函数上配置,我们都知道,在类活接口上的配置可以延续到类中的每个函数,那么,如果针对每个函数进行检测,在类本身上配置的事务属性岂不是检测不到了吗?带着这个疑问,我们继续探求matcher方法。

做匹配的时候methodMatcher.matches(method, targetClass)会使用TransactionAttributeSource Pointcut类的matches方法。

很遗憾,在getTransactionAttribute函数中并没有找到我们想要的代码,这里是指常规的一贯的 套路。尝试从缓存加载,如果对应信息没有被缓存的话,工作又委托给了computeTransactionAttribute函数,在computeTransactionAttribute函数中终于的我们看到了事务标签的提取过程。

提取事务标签

对于事务属性的获取规则相信大家都已经很清楚,如果方法中存在事务属性,则使用方法上的属性,否则使用方法所在的类上的属性,如果方法所在类的属性上还是没有搜寻到对应的事务属性,那么再搜寻接口中的方法,再没有的话,最后尝试搜寻接口的类上面的声明。对于函数cornputeTransactionAttribute中的逻辑与我们所认识的规则并无差别,但是上面函数中并没有真正的去做搜寻事务属性的逻辑,而是搭建了个执行框架,将搜寻事务属性的任务委托给了 findTransactionAttribute方法去执行。

this.annotationParsers是在当前类AnnotationTransactionAttributeSource初始化的时候初始化的,其中的值被加入了SpringTransactionAnnotationParser, 也就是当进行属性获取的时候其实是使用SpringTransactionAnnotationParser类的parseTransactionAnnotation方法进行解析的。

至此,我们终于看到了想看到的获取注解标记的代码。首先会判断当前的类是否含有 Transactional注解,这是事务属性的基础,当然如果有的话会继续调用parseTransactionAnnotation 方法解析详细的属性。

上面方法中实现了对对应类或者方法的事务属性解析,你会在这个类中看到任何你常用或者不常用的属性提取。

至此,我们终于完成了事务标签的解析。我们是不是分析的太远了,似乎已经忘了从哪里开 始了。再回顾一下,我们的现在的任务是找出某个增强器是否适合于对应的类,而是否匹配的关 键则在于是否从指定的类或类中的方法中找到对应的事务属性,现在,我们以UserServicelmpl 为例,已经在它的接口UserService中找到了事务属性,所以,它是与事务增强器匹配的,也就是它会被事务功能修饰。

至此,事务功能的初始化工作便结束了,当判断某个bean适用于事务增强时,也就是适用于增强器BeanFactoryTransactionAttributeSourceAdvisor, 没错,还是这个类,所以说,在自定义标签解析时,注入的类成为了整个事务功能的基础。

BeanFactoryTransactionAttributeSourceAdvisor作为Advisor的实现类,自然要遵从Advisor 的处理方式,当代理被调用时会调用这个类的增强方法,也就是此bean的Advise, 又因为在 解析事务定义标签时我们把Transactionlnterceptor类型的bean注入到了BeanFactoryTransactionAttributeSourceAdvisor中,所以,在调用事务增强器增强的代理类时会首先执行 Transactionlnterceptor进行增强,同时,也就是在Transactionlnterceptor类中的invoke方法中完 成了整个事务的逻辑。

事务增强器

TransactionInterceptor支撑着整个事务功能的架构,逻辑还是相对复杂的,那么现在我们切入 正题来分析此拦截器是如何实现事务特性的。TransactionInterceptor类继承自Methodlnterceptor, 所以调用该类是从其invoke方法开始的,首先预览下这个方法:

从上面的函数中,我们尝试整理下事务处理的脉络,在Spring中支持两种事务处理的方式,分别是声明式事务处理与编程式事务处理,两者相对于开发人员来讲差别很大,但是对于Spring中的实现来讲,大同小异。在invoke中我们也可以看到这两种方式的实现。考虑到对事务的应用比声明式的事务处理使用起来方便,也相对流行些,我们就以此种方式进行分析。对于声明式的事务处理主要有以下几个步骤。

1 获取事务的属性。

对于事务处理来说,最基础或者说最首要的工作便是获取事务属性了,这是支撑整个事务功能的基石,如果没有事务属性,其他功能也无从谈起,在分析事务准备阶段时我们已经分析了事务属性提取的功能,大家应该有所了解。

2 加载配置中配置的TransactionManager。

3 不同的事务处理方式使用不同的逻辑。

对于声明式事务的处理与编程式事务的处理,第一点区别在于事务属性上,因为编程式的事务处理是不需要有事务属性的,第二点区别就是在TransactionManager上,CalIbackPreferring PlatforrnTransactionManager实现PlatfonnTransactionManager接口,暴露出一个方法用于执行事务处理中的回调。所以,这两种方式都可以用作事务处理方式的判断。

4 在目标方法执行前获取事务并收集事务信息。

事务信息与事务属性并不相同,也就是Transactionlnfo与TransactionAttribute并不相同, Transactionlnfo中包含TransactionAttribute信息,但是,除了TransactionAttribute外还有其他事务信息,例如PlatformTransactionManager以及TransactionStatus相关信息。

5 执行目标方法。

6 一旦出现异常,尝试异常处理。 并不是所有异常,Spring都会将其回滚,默认只对RuntimeException回滚。

7 提交事务前的事务信息清除。

8 提交事务

上面的步骤分析旨在让大家对事务功能与步骤有个大致的了解,具体的功能还需要详细地分析。

创建事务

我们先分析事务创建的过程。

对于createTransactionltNecessar函数主要做了这样几件事情。

1 使用DelegatingTransactionAttribute封装传入的TransactionAttribute实例。

对于传入的TransactionAttribute类型的参数txAttr, 当前的实际类型是RuleBasedTransaction Attribute, 是由获取事务属性时生成,主要用于数据承载,而这里之所以使用Delegating TransactionAttribute进行封装,当然是提供了更多的功能。

2 获取事务。

事务处理当然是以事务为核心,那么获取事务就是最重要的事情。 3 构建事务信息。

根据之前几个步骤获取的信息构建Transactionlnfo并返回。 我们分别对以上步骤进行详细的解析。

获取事务

Spring中使用getTransaction来处理事务的准备工作,包括事务获取以及信息的构建。

当然,在Spring中每个复杂的功能实现,并不是一次完成的,而是会通过入口函数进行一个框架的搭建,初步构建完整的逻辑,而将实现细节分摊给不同的函数。那么,让我们看看事务的准备工作都包括哪些。

1 获取事务。

创建对应的事务实例,这里使用的是DataSourceTransactionManager中的doGetTransaction方法,创建基于JDBC的事务实例。如果当前线程中存在关于dataSource的连接,那么直接使用。这里有一个对保存点的设置,是否开启允许保存点取决于是否设置了允许嵌入式事务。

2 如果当先线程存在事务,则转向嵌套事务的处理。

3 事务超时设置验证。

4 事务propagationBehavior属性的设置验证。

5 构建DefaultTransactionStatus。

6 完善transaction, 包括设置ConnectionHolder、隔离级别、timeout, 如果是新连接,则绑定到当前线程。

对于一些隔离级别、timeout等功能的设置并不是由Spring来完成的,而是委托给底层的数据库连接去做的,而对于数据库连接的设置就是在doBegin函数中处理的。

可以说事务是从这个函数开始的,因为在这个函数中已经开始尝试了对数据库连接的获取,当然,在获取数据库连接的同时,一些必要的设置也是需要同步设置的。

1 尝试获取连接。

当然并不是每次都会获取新的连接,如果当前线程中的connectionHolder已经存在,则没有必要再次获取,或者,对于事务同步表示设置为true的需要重新获取连接。

2 设置隔离级别以及只读标识。

你是否有过这样的错觉?事务中的只读配置是Spring中做了一些处理呢? Spring中确实是针对只读操作做了一些处理,但是核心的实现是设置connection上的readOnly属性。同样,对于隔离级别的控制也是交由connection去控制的。

3 更改默认的提交设置。

如果事务属性是自动提交,那么需要改变这种设置,而将提交操作委托给Spring来处理。

4 设置标志位,标识当前连接已经被事务激活。

5 设置过期时间。

6 将connectionHolder绑定到当前线程。

设置隔离级别的prepareConnectionForTransaction函数用于负责对底层数据库连接的设置,当然,只是包含只读标识和隔离级别的设置。由于强大的日志及异常处理,显得函数代码量比较大,但是单从业务角度去看,关键代码其实是不多的。

7 将事务信息记录在当前线程中。

处理已经存在的事务

之前讲述了普通事务建立的过程,但是Spring中支持多种事务的传播规则,比如PROPAGATION_ NESTED、PROPAGATION _REQUIRES_ NEW等,这些都是在已经存在事务的基础上进行进一步的处理,那么,对于已经存在的事务,准备操作是如何进行的呢?

对于已经存在事务的处理过程中,我们看到了很多熟悉的操作,但是,也有些不同的地方,函数中对已经存在的事务处理考虑两种情况。

PROPAGATION_REQUIRES_NEW表示当前方法必须在它自己的事务里运行,一个新的事务将被启动,而如果有一个事务正在运行的话,则在这个方法运行期间被挂起。而Spring中对于此种传播方式的处理与新事务建立最大的不同点在于使用suspend方法将原事务挂起。将信息挂起的目的当然是为了在当前事务执行完毕后在将原事务还原。

PROPAGATION_NESTED表示如果当前正有一个事务在运行中,则该方法应该运行在一个嵌套的事务中,被嵌套的事务可以独立于封装事务进行提交或者回滚,如果封装事务不存在,行为就像PROPAGATION_REQUIRES_NEW。对于嵌入式事务的处理,Spring 中主要考虑了两种方式的处理。

Spring中允许嵌入事务的时候,则首选设置保存点的方式作为异常处理的回滚。

对于其他方式,比如JTA无法使用保存点的方式,那么处理方式与PROPAGATION_ REQUIRES_NEW相同,而一旦出现异常,则由Spring的事务异常处理机制去完成后续操作。

对于挂起操作的主要目的是记录原有事务的状态,以便于后续操作对事务的恢复:

准备事务信息

当已经建立事务连接并完成了事务信息的提取后,我们需要将所有的事务信息统一记录在 TransactionInfo类型的实例中,这个实例包含了目标方法开始前的所有状态信息,一旦事务执 行失败,Spring会通过Transactionlnfo类型的实例中的信息来进行回滚等后续工作。

回滚处理

之前已经完成了目标方法运行前的事务准备工作,而这些准备工作最大的目的无非是对于程序没有按照我们期待的那样进行,也就是出现特定的错误,那么,当出现错误的时候,Spring 是怎么对数据进行恢复的呢?

在对目标方法的执行过程中,一旦出现Throwable就会被引导至此方法处理,但是并不代表所有的Throwable都会被回滚处理,比如我们常用的Exception, 默认是不会被处理的。默认情况下,即使出现异常,数据也会被正常提交,而这个关键的地方就是在txlnfo.transaction Attribute.rollbackOn(ex)这个函数。

回滚条件

public boolean rollbackOn(Throwable ex) (

return (ex inscanceof RuntimeException I I ex instanceof Error);

看到了吗?默认情况下Spring中的事务异常处理机制只对RuntirneException和Error两种情况感兴趣,当然你可以通过扩展来改变,不过,我们最常用的还是使用事务提供的属性设置,利用注解方式的使用,例如:

@Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class)

回滚处理

当然,一旦符合回滚条件,那么Spring就会将程序引导至回滚处理函数中。

同样,对于在Spring中的复杂的逻辑处理过程,在入口函数一般都会给出个整体的处理脉络, 而把实现细节委托给其他函数去执行。我们尝试总结下Spring中对于回滚处理的大致脉络如下。

1 首先是自定义触发器的调用,包括在回滚前、完成回滚后的调用,当然完成回滚包括正常回滚与回滚过程中出现异常,自定义的触发器会根据这些信息作进一步处理,而对于触发器的注册,常见是在回凋过程中通过TransactionSynchronizationManager类中的静态方法直 接注册:

public static void registerSynchronization(TransactionSynchronization Synchionization)

2 除了触发监听函数外,就是真正的回滚逻辑处理了。

当之前已经保存的事务信息中有保存点信息的时候,使用保存点信息进行回滚。常用于 嵌入式事务,对于嵌入式的事务的处理,内嵌的事务异常并不会引起外部事务的回滚。

根据保存点回滚的实现方式其实是根据底层的数据库连接进行的。

这里使用的是JDBC的方式进行数据库连接,那么getSavepointManager()函数返回的是 JdbcTransactionObjectSuprort, 也就是说上面函数会调用JdbcTransactionObjectSupport中的 rollbackToSavepoint方法。

当之前已经保存的事务信息中的事务为新事务,那么直接回辰。常用于单独事务的处理。对于没有保存点的回滚,Spring同样是使用底层数据库连接提供的API来操作的。由于我们使用的是DataSourceTransactionManager, 那么doRollback函数会使用此类中的实现:

事务信息中表明是存在事务的,又不属于以上两种情况,多数用于JTA, 只做回滚标识,等到提交的时候统一不提交。

回滚后的信息清除

对于回滚逻辑执行结束后,无论回滚是否成功,都必须要做的事情就是事务结束后的收尾工作。

从函数中得知,事务处理的收尾处理工作包括如下内容。

设置状态是对事务信息作完成标识以避免重复调用。

如果当前事务是新的同步状态,需要将绑定到当前线程的事务信息清除。

如果是新事务需要做些清除资源的工作。

如果在事务执行前有事务挂起,那么当前事务执行结束后需要将挂起事务恢复。

事务提交

之前我们分析了Spring的事务异常处理机制,那么事务的执行并没有出现任何的异常,也就意味着事务可以走正常事务提交的流程了。

在真正的数据提交之前,还需要做个判断。不知道大家还有没有印象,在我们分析事务异常处理规则的时候,当某个事务既没有保存点又不是新事务,Spring对它的处理方式只是设置一个回滚标识。这个回滚标识在这里就会派上用场了,主要的应用场景如下。

某个事务是另一个事务的嵌入事务,但是,这些事务又不在Spring的管理范围内,或者无法设置保存点,那么Spring会通过设置回滚标识的方式来禁止提交。首先当某个嵌入事务发生回滚的时候会设置回滚标识,而等到外部事务提交时,一旦判断出当前事务流被设置了回滚标识,则由外部事务来统一进行整体事务的回滚。

所以,当事务没有被异常捕获的时候也并不意味着一定会执行提交的过程。

在提交过程中也并不是直接提交的,而是考虑了诸多的方面,符合提交的条件如下。

当事务状态中有保存点信息的话便不会去提交事务。

当事务非新事务的时候也不会去执行提交事务操作。

此条件主要考虑内嵌事务的清况,对于内嵌事务,在Spring中正常的处理方式是将内嵌事务开始之前设置保存点,一旦内嵌事务出现异常便根据保存点信息进行回滚,但是如果没有出现异常,内嵌事务并不会单独提交,而是根据事务流由最外层事务负责提交,所以如果当前存在保存点信息便不是最外层事务,不做保存操作,对于是否是新事务的判断也是基于此考虑。

如果程序流通过了事务的层层把关,最后顺利地进入了提交流程,那么同样,Spring会将事务提交的操作引导至底层数据库连接的API, 进行事务提交。

 

这篇关于spring源码分析 事务的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!