spring 事务源码赏析(一)
在本系列中,我们会分析:1.spring是如何开启事务的。2.spring是如何在不影响业务代码的情况下织入事务逻辑的。3.spirng事务是如何找到相应的的业务代码的。4.spring事务的传播行为是如何实现的。5.spring的事务隔离级别是如何实现的
因篇幅原因,本系列会分为两部分,第一部分会介绍前3个的实现,第二部分会介绍后两个的实现。
我们知道spring的事务管理分为两大部分:声明式和编程式,两种方式均为我们提供便捷的事务管理方法,各自优劣。
声明式的事务管理对业务代码基本0入侵,能够很好的把事务管理和业务代码剥离开来,提高代码扩展性和可读性但是控制的粒度只能是方法级别而且必须是public,同时还不能在一个类中调用等。
编程式事务则需要通过编写具体的事务代码来获得事务的管理能力,TransactionTemplate,或者直接使用PlatformTransactionManager,好处是控制粒度小,没有太多限制,坏处就是对业务代码有入侵,如果事务需要嵌套或者事务本身很繁琐,使用编程式则会十分麻烦。
虽然spring有两种事务管理方式,但是核心代码却是一份,本篇将从声明式事务做入口,和大家一起赏析spring的事务管理源码。
我们先从样例代码开始。
@Configuration
@ComponentScan("com.flyingrain.tx")
@EnableTransactionManagement
public class TxConfig {
@Autowired
private TxConf txConf; @Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
} @Bean
DataSource dataSource() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setUrl(txConf.getUrl());
basicDataSource.setUsername(txConf.getUserName());
basicDataSource.setPassword(txConf.getPassword());
basicDataSource.setDriverClassName(txConf.getDriverName());
return basicDataSource;
} }
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class)
public void transactionOperation(Employee employee){
employeeMapper.insert(employee);
employee.setId(1);
employee.setName("wulei");
employeeMapper.update(employee);
}
我们通过在spring配置类上加上@EnableTransactionManagement注解来开启spring的事务管理,通过@Transactional来告诉spring事务的开始,没错这和开启spring aop十分类似,我的另一篇博客有详细介绍:spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)。
这里再提一下,通过@Enable....注解,之所以能够开启一个功能,是因为在这些注解中,偷偷给容器注册了一些功能性的bean,通过bean的前置处理,或后置处理来改变bean的行为。比如@EnableAspectJAutoProxy中注入了AnnotationAwareAspectJAutoProxyCreator,在bean装载时通过后置处理代理特定的bean。
同样,@EnableTransactionManagement也注入了这样的bean:TransactionManagementConfigurationSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE; }
通过@Import注解在spring初始化时调用TransactionManagementConfigurationSelector的selectImports方法,装载指定的bean,至于spring何时何地如何解析装载@Import注解中的类的,请看我spring5之容器始末源码赏析系列相关博客,这个系列详细介绍了spring使如何启动的本篇就不做赘述。
TransactionManagementConfigurationSelector主要责任是根据配置选择特定的bean装载进容器,这里有两个选择,通过EnableTransactionManagement 的mode()方法配置,默认是PROXY,这个配置的作用是告诉spring如何寻找事务的切面,是通过aspectJ的方式,还是spring自己的事务切面,一般我们使用默认的方式:使用spring自己的事务切面。如果选择aspecJ的话还需要引入spring aspects相关依赖。本篇着重介绍PROXY模式,aspectJ的话本质上同proxy是一样的。
我们选择PROXY模式后,TransactionManagementConfigurationSelector 将会在spring中加入两个bean:AutoProxyRegistrar,ProxyTransactionManagementConfiguration,一个是实现了ImportBeanDefinitionRegistrar,所有实现了这个接口的类都会在spring初始化时被调用registerBeanDefinitions方法(如何以及何时被调用,请参考spring5之容器始末源码赏析系列相关博客)。另一个是有@Configuration的配置类。我们先看AutoProxyRegistrar:
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { private final Log logger = LogFactory.getLog(getClass()); @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
for (String annoType : annoTypes) {
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
if (candidate == null) {
continue;
}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
candidateFound = true;
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
if (!candidateFound) {
String name = getClass().getSimpleName();
logger.warn(String.format("%s was imported but no annotations were found " +
"having both 'mode' and 'proxyTargetClass' attributes of type " +
"AdviceMode and boolean respectively. This means that auto proxy " +
"creator registration and configuration may not have occurred as " +
"intended, and components may not be proxied as expected. Check to " +
"ensure that %s has been @Import'ed on the same class where these " +
"annotations are declared; otherwise remove the import of %s " +
"altogether.", name, name, name));
}
} }
我们看到这个方法把注册bean的任务委托给了AopConfigUtils.registerAutoProxyCreatorIfNecessary,继续往下看:
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
@Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
} RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
它实际上是注册了InfrastructureAdvisorAutoProxyCreator这个bean,并设置角色为基础spring内部的基础bean,这个‘bean就有点意思了,它跟我们之前分析的spring aop源码用的是统一个父类
结合spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)。中的分析我们可以得出,这个类就是开启transaction切面的类,它的核心代码和aop的原理一样,实现了BeanPostProcessor接口,在bean装载完成后会调用postProcessAfterInitialization,在postProcessAfterInitialization方法中会去加载切面,并且根据切点匹配相应的方法,对对象进行动态代理。具体的代码在父类AbstractAutoProxyCreator中,并且我们在spring @EnableAspectJAutoProxy背后的那些事(spring AOP源码赏析)也分析过,这里不在赘述。
所以AutoProxyRegistrar就是负责装载InfrastructureAdvisorAutoProxyCreator,来开启事务的代理,那么用来代理的切面和切点在哪里呢,没错,另一个配置类
ProxyTransactionManagementConfiguration,就是用来装载事务的切面和切点:
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
} }
这里主要有3个类:
BeanFactoryTransactionAttributeSourceAdvisor,AnnotationTransactionAttributeSource,TransactionInterceptor
这三个类的作用分别是:BeanFactoryTransactionAttributeSourceAdvisor为事务aop提供建言advisor,里面包含了切点的判定AnnotationTransactionAttributeSource,和切面的逻辑:TransactionInterceptor。
我们先来看一下切点的判定,spring通过切点的matches方法来判断是否需要对目标对象进行代理:
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override
public boolean matches(Method method, @Nullable Class<?> targetClass) {
if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) {
return false;
}
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
} //。。。。。此处省略不相关代码 }
主要看标红的那行,我们看到,其实判断是否需要事务,就是通过tas(AnnotationTransactionAttributeSource)的getTransactionAttribute方法返回是否为空决定的,我们来到AnnotationTransactionAttributeSource的父类AbstractFallbackTransactionAttributeSource来看看它如何获取TransactionAttribute的:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Ignore CGLIB subclasses - introspect the actual user class.
Class<?> userClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
} // Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
} if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
} return null;
}
我们看到,第一行就是判断方法的访问权限是否是public,否则直接返回null,后面是对class的处理,防止是cglib生成的代理类,关键代码看findTransactionAttribute方法
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
if (attr != null) {
return attr;
}
}
return null;
}
我们看到,spring把解析注解的功能委托给TransactionAnnotationParser,而TransactionAnnotationParser的实现有3个SpringTransactionAnnotationParser,JtaTransactionAnnotationParser,Ejb3TransactionAnnotationParser,SpringTransactionAnnotationParser是在启动时默认装载的,剩下两个spring会根据类路径下是否存在相关类来判断是否需要装载,我们来看SpringTransactionAnnotationParser:
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
ae, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<>();
Class<?>[] rbf = attributes.getClassArray("rollbackFor");
for (Class<?> rbRule : rbf) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
String[] rbfc = attributes.getStringArray("rollbackForClassName");
for (String rbRule : rbfc) {
RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
Class<?>[] nrbf = attributes.getClassArray("noRollbackFor");
for (Class<?> rbRule : nrbf) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
for (String rbRule : nrbfc) {
NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
rollBackRules.add(rule);
}
rbta.getRollbackRules().addAll(rollBackRules);
return rbta;
}
可以看到,在这里主要是解析@transactional注解,并返回,到此,事务的aop已经能够匹配到具体的切点了,剩下的就是把切面了逻辑也加进去。
总结:本篇主要介绍了事务的前一部分:aop的开启和切点的匹配,spring通过@EnableTransactionManagement注解注入InfrastructureAdvisorAutoProxyCreator这个aop的基础组件,然后提供切点TransactionAttributeSourcePointcut匹配要被代理的方法,最后生成代理加入切面逻辑。
如果把事务比作机器,那@EnableTransactionManagement便是开关,InfrastructureAdvisorAutoProxyCreator是机器的外壳,提供机器运作所需的条件,TransactionAttributeSourcePointcut是齿轮,只有匹配上了才能运作,TransactionInterceptor是机器的核心,也可以说是发动机,我们将在下一篇单独介绍事务的核心逻辑:TransactionInterceptor,我们将会看到:
1.事务的传播行为的实现
2.事务的隔离级别的实现
3.事务生命周期的管理
本篇到此结束,有不足或理解错误的地方希望大家指正!谢谢~
转载请注明出处~
spring 事务源码赏析(一)的更多相关文章
- spring 事务源码赏析(二)
我们在spring 事务源码赏析(一) 中分析了spring事务是如何找到目标方法,并如何将事务的逻辑织入到我们的业务逻辑中.本篇我们将会看到spring事务的核心实现: 1.事务传播机制的实现 2. ...
- spring事务源码研读1
转载摘录自:Spring事务源码分析(一)Spring事务入门 有时为了保证一些操作要么都成功,要么都失败,这就需要事务来保证. 传统的jdbc事务如下: @Test public void test ...
- spring事务源码解析
前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...
- 结合ThreadLocal来看spring事务源码,感受下清泉般的洗涤!
在我的博客spring事务源码解析中,提到了一个很关键的点:将connection绑定到当前线程来保证这个线程中的数据库操作用的是同一个connection.但是没有细致的讲到如何绑定,以及为什么这么 ...
- 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)
一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...
- [心得体会]spring事务源码分析
spring事务源码分析 1. 事务的初始化注册(从 @EnableTransactionManagement 开始) @Import(TransactionManagementConfigurati ...
- Spring事务源码阅读笔记
1. 背景 本文主要介绍Spring声明式事务的实现原理及源码.对一些工作中的案例与事务源码中的参数进行总结. 2. 基本概念 2.1 基本名词解释 名词 概念 PlatformTransaction ...
- spring事务源码分析结合mybatis源码(一)
最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...
- Spring系列(六):Spring事务源码解析
一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...
随机推荐
- redis01
1.redis 1)cookie与session session本质上也是cookie,cookie携带session返回给服务端 redis是一个存储数据库 redis读写快速,使用简单,常用于存储 ...
- 全差分运算放大器ADA4930的分析(1)
AD转换芯片的模拟信号输入端方式为:全差分.伪差分.单端输入,其中全差分输入的效果最佳,现阶段ADC转换器为了提高其性能,建议用户使用全差分的输入方式.(AD7982.ADS8317等都能实现信号的全 ...
- JS基础入门篇(二十)—事件对象以及案例(二)
案例一.点击按钮,选中input中的全部内容 select()方法:选中全部. 点击按钮选中输入框中的内容!!!! <!DOCTYPE html> <html lang=" ...
- Windows下利用virtualenvwrapper指定python版本创建虚拟环境
默认已安装virtualenvwrapper 一.添加环境变量(可选) 在系统环境变量中添加 WORKON_HOME ,用来指定新建的虚拟环境的存储位置,如过未添加,默认位置为 %USERPROFIL ...
- PAT-字符串处理-B1006 换个格式输出整数 (15分)
题目描述: 让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数.例如 234 应该被输出为 ...
- 【5min+】AspNet Core中的全局异常处理
系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...
- Vue项目二、vue-cli2.x脚手架搭建build文件夹及config文件夹详解
build文件夹下 build.js 'use strict' // js的严格模式 require('./check-versions')() // node和npm的版本检查 process.en ...
- ios background task
今天要实现一个需求,当用户触摸HOME键,将应用切换到后台时,启动自动备份的任务.这涉及到ios的后台任务处理,本文简单总结一下 首先,ios app有5种状态,分别是:not running, in ...
- Flask 使用pycharm 创建项目,一个简单的web 搭建
1:新建项目后 2:Flask web 项目重要的就是app 所有每个都需要app app=Flask(__name__) 3:Flask 的路径是有app.route('path')装饰决定, ...
- 【GTS-Fail】GtsSecurityHostTestCases#testNoExemptionsForSocketsBetweenCoreAndVendorBan
[GTS-Fail]GtsSecurityHostTestCases#testNoExemptionsForSocketsBetweenCoreAndVendorBan [问题描述] Gts-7.0- ...