Spring中的事物源码解析

2023-11-16

前言

在Spring中,使用事物的方式基本上都是通过声明@Transactional来完成的。

xml方式

在xml的IOC容器中<tx:annotation-driven/>开启Spring的声明式事物支持。

Spring事物xml标签的解析由TxNamespaceHandler来处理。

<tx:annotation-driven/>该标签的解析由AnnotationDrivenBeanDefinitionParser来解析处理。

Spring声明式事物涉及三个核心类:

  • BeanFactoryTransactionAttributeSourceAdvisor:
    在bean的初始化中,有一步populateBean,为该bean设置属性对象。
    属性是从哪里赋值的呢?
    AnnotationDrivenBeanDefinitionParser解析该xml标签的时候,通过代码添加的。

    /**
    	 * Parses the {@code <tx:annotation-driven/>} tag. Will
    	 * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
    	 * with the container as necessary.
    	 */
    	@Override
    	public BeanDefinition parse(Element element, ParserContext parserContext) {
    		registerTransactionalEventListenerFactory(parserContext);
    		String mode = element.getAttribute("mode");
    		if ("aspectj".equals(mode)) {
    			// mode="aspectj"
    			registerTransactionAspect(element, parserContext);
    		}
    		else {
    			// mode="proxy"
    			// 默认走这里,为该beanDefinition添加属性值,以便创建该bean的时候
    			// 其field域的属性能被赋值。
    			AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
    		}
    		return null;
    	}
    	
    

    populateBean()的该对象的属性值的时候,触发AnnotationTransactionAttributeSource对象和TransactionInterceptor的创建。即transactionAttributeSource属性adviceBeanName属性

  • AnnotationTransactionAttributeSource:
    AnnotationTransactionAttributeSource(true)决定了@Transactional只能应用在public方法中。
    值为true,代表@Transanctional只能应用在public方法中,且是基于AOP代理的
    值为false,代表@Transactional可以应用在protected or private方法中,且是基于AspectJ代理的

  • TransactionInterceptor:

  • DataSourceTransactionManager:

注解的方式

通过@EnableTransactionManagement来开启Spring事物的声明式管理,这个注解引入TransactionManagementConfigurationSelector

	/**
	 * Returns {@link ProxyTransactionManagementConfiguration} or
	 * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
	 * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
	 * respectively.
	 */
	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {
				// 引入SpringAOP处理,即AnnotationAwareAspectJAutoProxyCreator
				AutoProxyRegistrar.class.getName(),
				// 事物配置类,即开启声明式事物所需的三个核心对象
				ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}

该注解的解析是由ConfigurationClassPostProcessor来处理成可供IOC容器生成bean的BeanDefinition。【processImports阶段来处理该ImportSelector

AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor通过postProcessBeforeInstantiation来触发Spring声明式事物的三个核心类的Bean对象创建(findAdvisorBeans())。在这个过程中,触发对配置类ProxyTransactionManagementConfiguration的创建。

通过ProxyTransactionManagementConfiguration的创建来看ImportAware的作用?

initializationBean阶段中交由ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor处理ImportAware接口。有什么作用呢?获取该bean的注解元数据。

	// ImportAwareBeanPostProcessor 获取一个bean的注解元数据
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		if (bean instanceof ImportAware) {
			ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
			AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
			if (importingClass != null) {
				 // 设置注解元数据
				((ImportAware) bean).setImportMetadata(importingClass);
			}
		}
		return bean;
	}
	
	// 为ProxyTransactionManagementConfiguration设置注解元数据
	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		this.enableTx = AnnotationAttributes.fromMap(
				importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
		if (this.enableTx == null) {
			throw new IllegalArgumentException(
					"@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName());
		}
	}

代理对象

代理对象的生成是在bean初始化的initializationBean阶段,即advisedBeans缓存,防止重复代理bean对象(@Aspect注解的类不会被代理)。

    // 在doCreateBean之前,接口代理处理,会给advisedBeans赋值
	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		// 这里,在AbstractAutoProxyCreator中,以下情况不能代理
		// 情况一:该类角色是基础设施类或者用切面注解,isInfrastructureClass(beanClass)
		// 情况二:shouldSkip,是否应该跳过代理
		// 是这两种情况,不能AOP代理,用advisedBeans存放判断结果
		// this.advisedBeans.put(cacheKey, Boolean.FALSE); return null;
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	}

生成的代理对象只有一个,对于自定义AOP跟@Transactional同切一个方法,也只对该方法所在类生成一个代理对象,所不同的是自定义的AOP切面对应的是InstantiationModelAwarePointcutAdvisorBeanFactoryTransactionAttributeSourceAdvisor这两个Advisor进行针对性切面匹配处理。

@Aspect进行自定义切面的,防止对方法的重复切逻辑匹配判断处理, 用shadowMatchCache进行缓存。
@Transactional进行事物切面的,防止对方法的重复切逻辑匹配判断处理, 用attributeCache进行缓存。【因为,匹配的过程,有关类下的所有方法进行匹配,且当执行该事物方法时,会重新再通过attributeCache获取TransactionAttribute。】

为什么要有这两个缓存?
因为,只要开启Aop和事物,那么在IOC容器初始化的bean的创建中,总会有Bean后置处理器AnnotationAwareAspectJAutoProxyCreator对该bean进行处理。那么,如果该bean需要代理,就会每次都走该处理器的父类AbstractAutoProxyCreatorwrapIfNecessary()方法。

	/**
	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @param cacheKey the cache key for metadata access
	 * @return a proxy wrapping the bean, or the raw bean instance as-is
	 */
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		// 通过缓存的advisedBean先判断是否代理
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		// 基础设施类或者Aop类以及shouldSkip不代理
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		// 是否有针对该bean代理处理的Advisor
		// 这个获取,就是防止重复处理,用缓存进行
		// 如果是AOP,用shadowCache;如果是Transaction,用attributeCache缓存
		// 该方法对应的TransactionAttribute
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

这一过程发生在findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName)阶段中。

@Aspect@Transactional都是被AnnotationAwareAspectJAutoProxyCreator来处理的。而@Async是被AsyncAnnotationBeanPostProcessor所来处理的。
完整分析可以参考IOC容器初始化之AOP内部机制

事物执行

在执行事物方法(被切方法)时,防止重复进入创建方法执行器并处理,用methodCache方法缓存,缓存该方法的拦截器或者Advise链(是List<Object>),因为该方法会被不同切面处理,比如这个场景:自定义的AOP切这个方法和用 @Transactional切这个方法。同时,这个场景需要注意,自定义的切面如果跟声明式事物同切一个方法时,一定要注意,自定义切面的通知方法,显示抛出异常,否则,异常被吃掉,导致声明式事物不起作用。

会进入CglibAopProxy的intercept方法【方法拦截】。

获取到该方法的所有的拦截器InterceptorAdvice

	// 1. 获取AOP中所有的Advisor
	// 2. 从所有的Advisor中,筛选出它们的匹配器能匹配到该方法的MethodInterceptor
	// 在这个matches的过程中会用到TransactionAttribute,因此是从缓存中获取的。
	List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

创建CglibMethodInvocation方法执行器进行proceed()处理。

进入TransactionInterceptor的invoke处理,即在事物的情形下执行:
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);

1.获取事务属性源TransactionAttributeSource
	怎么来的?即是TransactionInterceptor中的属性值。
	在创建TransactionInterceptor对象的时候赋值。
		=> ProxyTransactionManagementConfiguration
		
2.从TransactionAttributeSource中获取事务属性TransactionAttribute。
	这里是从缓存中获取的,第一次创建是在创建事物方法所在的类的代理对象的初始化bean阶段,
	用AnnotationAwareAspectJAutoProxyCreator创建代理对象的时候,将@Transactional
	转换成TransactionAttribute。
	
3.从IOC容器中获取事务管理器TransactionManager【DataSourceTransactionManager】。
4.将事物管理器对象强转及获取事物方法信息(methodJoinPointInfo,以此作为事物名【默认】)5.根据事物属性,事务管理器,事物方法信息创建事物【createTransactionIfNecessary】
	(这里,注意一下命名,获取的是事物信息TransactionInfo,但是方法命名却是Transaction,
	简化方法的命名,不是根据返回值命名,而是根据语义命名。)
	
	5.1 根据事物属性从事务管理器中获取事物【TransactionStatus】
	(这里的方法名同上,也是没有命名为getTransactionStatus,而是getTransaction)
		
		5.1.1 真正获取事物(方法命名,doGetTransaction,且这是一个模板方法,实际实现,
		交由具体的事务管理器实现,比如DataSourceTransactionManager)
			创建一个事物对象,DataSourceTransactionObject
			给对象设定相应属性值,比如,嵌套事物,Jdbc连接
			Jdbc连接是怎么获取的?
			从事物同步管理器中获取:
			TransactionSynchronizationManager.getResource(obtainDataSource())
			事物同步管理器,通过NameThreadLocal将线程与事物相关信息进行了绑定,通过
			ThreadLocal,我们也不难看出,Spring的事物是本地事物。

		5.1.2 已经存在事物【场景:嵌套事物】
			判断事物存在的方法:
				就是判断新建事物(事物对象)中是否含有Jdbc连接(Holder)或者Jdbc连接
				(Holder)中的事物活跃
				
			处理存在的事物:handleExistingTransaction,依照事物的传播机制分场景处理

		5.1.3 当前不存在事物,依照事物的传播机制分场景处理
			不支持这种传播机制:TransactionDefinition.PROPAGATION_MANDATORY
			
			该事物方法定义的传播行为是以下三种:
				TransactionDefinition.PROPAGATION_REQUIRED
				TransactionDefinition.PROPAGATION_REQUIRES_NEW
				TransactionDefinition.PROPAGATION_NESTED
			挂起事物:suspend(null)
			
			开始事务:startTransaction
				创建事物状态TransactionStatus
				实际开始事务:doBegin(方法命名,没有起名doBeginTransaction,
				因为语境已是开始事物阶段)
				
					事物对象中不存在连接Holder或者连接没有到事物同步阶段
						从数据源中获取连接对象,为事物对象设置连接对象
					为事物对象设置事物同步标志true,到事物同步阶段
					为事物对象设置隔离级别,只读标识
					Jdbc连接自动提交:
						设置为非自动提交(因此,Spring事物都是非自动提交方式)
					准备事物连接:prepareTransactionalConnection
						事物是否只读
					为事物对象的连接Holder设置事物活跃标志,true
					事物对象是新创建的,绑定连接到当前线程ThreadLocal:
						TransactionSynchronizationManager.bindResource(
							obtainDataSource(), 
							txObject.getConnectionHolder());
					在整个doBegin阶段中抛异常的话:
						事物对象是新创建的:
							释放Jdbc连接
							为该事物对象的connectionHolder置空
				准备同步事物,绑定到线程:prepareSynchronization
			开始事物这个阶段抛异常:
				恢复挂起的事物资源:
					resume(null, suspendedResources);
	
	5.2 准备事物信息:prepareTransactionInfo
			将事物信息绑定到当前线程:
			原理的事物信息,赋值给oldTransactionInfo
6. 继续处理,多个切点,切的是同一个方法:
	invocation.proceedWithInvocation()
	最终,会再次进入ReflectiveMethodInvocation的proceed方法
	判断,事物方法的拦截器链执行完,执行实际方法invokeJoinPoint(被声明式事物注解的方法)
7.  执行被声明式事物注解的方法
	执行jdbc的方法的时候都没去提交。
8. 当执行完清除事物信息的时候cleanupTransactionInfo,提交事物:
	commitTransactionAfterReturning(txInfo);	
	
	通过事务管理器提交事物状态:commit
	
	是否回滚
	处理提交:processCommit
		提交前的准备:prepareForCommit
		提交前触发回调:triggerBeforeCommit
		事物完成前触发回调:triggerBeforeCompletion
		根据事物状态属性执行不同动作:
			有保存点savePoint,释放
			事物状态是一个新事物的状态,实际提交事物状态:doCommit(status)
	    提交事物之后触发回调:triggerAfterCommit
	    事物完成后触发回调:triggerAfterCompletion
	    	清除事物同步器
	    事物完成后清除:cleanupAfterCompletion
	    	事物状态的完成属性置为true
	    	事物同步器清除:TransactionSynchronizationManager.clear();
	    	完成之后实际清除事物:doCleanupAfterCompletion
	    		将数据源与当前线程解绑:		
					TransactionSynchronizationManager.unbindResource(obtainDataSource());
				重置Jdbc连接为自动提交
				重置事物之后的连接:
					DataSourceUtils.resetConnectionAfterTransaction(
					con, txObject.getPreviousIsolationLevel(), 
					txObject.isReadOnly());
					释放连接
					清除事物对象中的连接Holder
9.一次事物方法的执行已经完成
									
						
					
					
			
			
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring中的事物源码解析 的相关文章

随机推荐

  • html自动填充高度,div 自适应高度,自动填充剩余高度

    方案1 头部 下部 样式 html body height 100 padding 0 margin 0 outer height 100 padding 100px 0 0 box sizing border box position r
  • JAVA知识点-BeanUtils.copyProperties() 用法

    本文为转载文章 转载地址 https www cnblogs com ahri gx p 10551340 html 文章只做学习交流使用 欢迎指正 2022 08 10更新 至于谁赋值给谁 看你导的包 package org spring
  • 二叉链表之寻找两节点的最近公共祖先☆

    题目 p q分别为指向该二叉树中任意两个节点的指针 试编写算法ancestor root p q r 找到p q的最近公共祖先节点r 分析 上一道题其实可以给我们一些启示 就是我们可以将任意节点的祖先存起来 那这里我们也可以用两个栈 分别将
  • Column 2 has an invalid name and/or length

    不要和我说你不认识英文 哈哈 不认识英文就去学 https archive sap com discussions thread 3878660 Hello I just got an SAP incident which looks si
  • python爬虫简单js逆向

    python爬虫简单js逆向案例 内容简介 一 找到包含所需数据的ajax数据包 二 通过浏览器工具进行关键字定位 三 分析相关js文件 找出具体实现方式 1 getApiKey 函数 2 encryptApiKey 函数 3 encryp
  • jsp中标签id和name的区别

    name原来是为了标识之用 但是现在根据规范 都建议用id来标识元素 但是name在以下用途是不能替代的 1 表单 form 的控件名 提交的数据都用控件的name而不是id来控制 因为有许多name会同时对应多个控件 比如checkbox
  • 二十、线程安全

    文章目录 一 线程安全 一 概念 二 线程安全之临界资源 三 线程安全之可重入函数 1 基本概念 2 实例 二 线程和fork 一 fork后子进程线程数量 二 fork后子进程锁的处理 一 线程安全 一 概念 线程安全 就是在多线程运行的
  • 【Locomotor运动模块】抓取:按朝向抓取(Orientation Handler)案例

    文章目录 案例 原理 案例 左右手柄抓宝剑时 宝剑的朝向不同 L35 一个手柄对应一个抓取点 原理 1 左右手柄分别抓取的是宝剑上的不同抓取点 GenericOrientation Handle通用朝向把手 它是我们设置 按朝向抓取 Ori
  • 11月1日任务

    11月1日任务 10 32 10 33 rsync通过服务同步 10 34 linux系统日志 10 35 screen工具 扩展 1 Linux日志文件总管logrotate http linux cn article 4126 1 ht
  • L2TP详解(二)

    今天继续给大家介绍HCIE安全 本文给大家介绍的是L2TP相关内容 包括L2TP的特点和应用场景 强烈推荐阅读本文前置文章 L2TP详解 一 一 L2TP隧道和会话建立过程 在LAC和LNS之间存在着两种类型的链接 隧道链接和会话链接 隧道
  • 很详细的系列Shell基础— Shell简介

    一 Shell的由来 我们比较熟悉Windows系统的图形化界面 对于图形界面来说 用户点击某个图标就能启动某个程序 在此之前我们一直在使用Linux系统的命令行模式学习 对于命令行来说 用户输入一个命令就能启动某个程序 这两者的基本过程都
  • 图解CRM(客户关系管理)全流程

    在不同场合下 CRM 客户关系管理 可能是一个管理学术语 也可能是一个软件系统 我们通常所指的CRM 指用计算机自动化分析销售 市场营销 客户服务以及应用等流程的软件系统 通俗地说 CRM就是利用软件 硬件和网络技术 为企业建立一个客户信息
  • 测试开发工程师面试总结(一)——Java基础篇

    本文面向对象 测试开发工程师 服务端自动化方向 随手百度一下都能找到 岗位面试总结 但是有关测开岗位的面试总结却寥寥无几 总体原因可能是这两个 1 测试行业整体水平参差不齐 导致不同公司面试的问题不能抽象出来写概览 2 很多做测开的人可能内
  • IDEA maven项目依赖无法解析问题

    这篇文章主要介绍了IDEA maven项目依赖无法解析问题 具有很好的参考价值 希望对大家有所帮助 如有错误或未考虑完全的地方 望不吝赐教 目录 IDEA maven项目依赖无法解析 问题排除 配置文件setting xml内容是否正确 p
  • export ‘default‘ (imported as ‘components‘) was not found in

    vue自己写了组件文件包components爆了这个错 原因是components文件夹下的index js文件 import tabbar from tabbar export default tabbar 没有default关键字 加上
  • nodejs 控制台美化 console-color-mr

    console color mr插件可以让node控制台输出带有颜色 是一个不错的插件 通过颜色可以更直观的分析程序bug 一 使用 npm install D console color mr 方法一 import console col
  • 直流电机驱动

    1 直流电机是一种将电能转化为机械能的装置 一般的直流电机有两个电极 当电极正接时 电机正转 当电机反接时 电机反转 2 PWM 脉冲宽度调制 PWM调速的原理就是 我让他转5us 停1us表示转速快 相反 我让他转1us 停5us表示慢
  • nokia专业显示器测试软件,Nokia Monitor Test(

    Nokia Monitor Test 显示器测试软件 是一款计算机显示屏的专业测试工具 使用Nokia Monitor Test 显示器测试软件 可以检测显示器的亮度 对比度 色纯 聚焦 水波纹 抖动 可读性等重要显示效果和技术参数 而这些
  • 以太坊的安装、私有链创世区块搭建以及智能合约的部署

    相关阅读 区块链项目 区块链网上安全商铺 合约代码 前端 后台 文章目录 1 以太坊的安装 私有链创世区块搭建 私有链节点的加入 1 1 以太坊的安装 1 2 私有链创世区块搭建 1 3 私有链节点的加入 2 对getBlock中所得区块的
  • Spring中的事物源码解析

    目录 前言 xml方式 注解的方式 代理对象 事物执行 前言 在Spring中 使用事物的方式基本上都是通过声明 Transactional来完成的 xml方式 在xml的IOC容器中