Spring学习笔记和源码分析续集-赢在CSDN

书接上回,上篇写到BeanFactory创建流程的源码分析,咱们继续分析源码,更加深入了解Spring。

Bean创建流程

  • 通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在
    AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory)处
    在这里插入图片描述
  • 进⼊finishBeanFactoryInitialization

在这里插入图片描述

  • 继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例
    在这里插入图片描述

  • 继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法,这个⽅法中的代码很多,我们直接找到核⼼部分
    在这里插入图片描述

  • 接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法,找到以下代码部分
    在这里插入图片描述

  • 进⼊doCreateBean⽅法看看,该⽅法我们关注两块重点区域

    • 创建Bean实例,此时尚未设置属性
      在这里插入图片描述

    • 给Bean填充属性,调⽤初始化⽅法,应⽤BeanPostProcessor后置处理器
      在这里插入图片描述
      至此已了解Bean创建的流程,细节部分先不关注,重点把握主流程

lazy-init 延迟加载机制原理

lazy-init 延迟加载机制分析

普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。

public void preInstantiateSingletons() throws BeansException {
	// 所有beanDefinition集合
	List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
	// 触发所有⾮懒加载单例bean的初始化
	for (String beanName : beanNames) {
		// 获取bean 定义
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			// 判断是否是 FactoryBean
			if (isFactoryBean(beanName)) {
				final FactoryBean<?> factory = (FactoryBean<?>)
				getBean(FACTORY_BEAN_PREFIX + beanName);
				boolean isEagerInit;
				if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
					isEagerInit = AccessController.doPrivileged(newPrivilegedAction<Boolean>() {
						@Override
						public Boolean run() {
							return ((SmartFactoryBean<?>) factory).isEagerInit();
					 	}
					 }, getAccessControlContext());
				  }
			}else {
			/**
			如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
			和
			懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
			*/
			getBean(beanName);
		}
	 }
	}
}

总结

  • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次进⾏getBean时候才进⾏初始化并依赖注⼊
  • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean已经 初始化完成并缓存了起来

Spring IoC循环依赖问题

什么是循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
在这里插入图片描述
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
Spring中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)
    其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。
循环依赖处理机制
  • 单例 bean 构造器参数循环依赖(⽆法解决)
  • prototype 原型 bean循环依赖(⽆法解决)
    对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。
    AbstractBeanFactory.doGetBean()⽅法:
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null &&
	 (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
	curVal).contains(beanName))));
}

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记

try {
	//创建原型bean之前添加标记
	beforePrototypeCreation(beanName);
	//创建原型bean
	prototypeInstance = createBean(beanName, mbd, args);
}finally {
	//创建原型bean之后删除标记
	afterPrototypeCreation(beanName);
}

总结:Spring 不⽀持原型 bean 的循环依赖。

  • 单例bean通过setXxx或者@Autowired进⾏循环依赖
    Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前

    Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

    • Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
    boolean earlySingletonExposure = (mbd.isSingleton() &&
    this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    	if (earlySingletonExposure) {
    		if (logger.isDebugEnabled()) {
    			logger.debug("Eagerly caching bean '" + beanName +
    			"' to allow for resolving potential circular references");
    		}
    		//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
    		addSingletonFactory(beanName, new ObjectFactory<Object>() {
    			@Override
    			public Object getObject() throws BeansException {
    				return getEarlyBeanReference(beanName, mbd, bean);
    			}
    		});
    }
    
    • ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
    • Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
    • ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例
      • ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
    • 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。

Spring AOP 应⽤

AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。

AOP 相关术语
业务主线

在讲AOP术语之前,我们先来看⼀下下⾯这两张图,它们就是第三部分案例需求的扩展(针对这些扩展的需求,我们只进⾏分析,在此基础上去进⼀步回顾AOP,不进⾏实现)
在这里插入图片描述
上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
在这里插入图片描述

AOP 术语
名词解释
Joinpoint(连接点)它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
Pointcut(切⼊点)它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强
Advice(通 知/增强)它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。
Target(⽬标对象)它指的是代理的⽬标对象。即被代理对象。
Proxy(代理)它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
Weaving(织 ⼊)它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
Aspect(切 ⾯)它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager 就是⼀个切⾯。
  • 连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
  • 切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法
  • Advice增强
    • 第⼀个层次:指的是横切逻辑
    • 第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
  • Aspect切⾯:切⾯概念是对上述概念的⼀个综合
    • Aspect切⾯= 切⼊点+增强
    • = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑

众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码

Spring中AOP的代理选择

Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。

  • 当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。
  • 当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,
  • 不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
Spring中AOP的配置⽅式

在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。

  • 第⼀类:使⽤XML配置
  • 第⼆类:使⽤XML+注解组合配置
  • 第三类:使⽤纯注解配置

XML配置

<!--
Spring基于XML的AOP配置前期准备:
	在spring的配置⽂件中加⼊aop的约束
	xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
	Spring基于XML的AOP配置步骤:
		第⼀步:把通知Bean交给Spring管理
		第⼆步:使⽤aop:config开始aop的配置
		第三步:使⽤aop:aspect配置切⾯
		第四步:使⽤对应的标签配置通知的类型
		⼊⻔案例采⽤前置通知,标签为aop:before
-->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.learn.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
	<!--配置切⾯-->
	<aop:aspect id="logAdvice" ref="logUtil">
		<!--配置前置通知-->
		<aop:before method="printLog" pointcut="execution(public *
com.learn.service.impl.TransferServiceImpl.updateAccountByCardNo(com.learn
		.pojo.Account))"></aop:before>
	</aop:aspect>
</aop:config>
  • 关于切⼊点表达式

    • 概念及作⽤
      • 切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。
    • 关于AspectJ
      • AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。
    • 切⼊点表达式使⽤示例
    	全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
    	全匹配⽅式:
    	public void
    	com.learn.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    	om.learn.pojo.Account)
    	
    	访问修饰符可以省略
    	void
    	com.learn.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    	om.learn.pojo.Account)
    	
    	返回值可以使⽤*,表示任意返回值
    	*
    	com.learn.service.impl.TransferServiceImpl.updateAccountByCardNo(c
    	om.learn.pojo.Account)
    	
    	包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
    	*
    	....TransferServiceImpl.updateAccountByCardNo(com.learn.pojo.Accou
    	nt)
    	
    	包名可以使⽤..表示当前包及其⼦包
    	*
    	..TransferServiceImpl.updateAccountByCardNo(com.learn.pojo.Account
    	)
    	
    	类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
    	* ...(com.learn.pojo.Account)
    	
    	参数列表,可以使⽤具体类型
    	基本类型直接写类型名称 : int
    	引⽤类型必须写全限定类名:java.lang.String
    	参数列表可以使⽤*,表示任意参数类型,但是必须有参数
    	* *..*.*(*)
    	
    	参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
    	* *..*.*(..)
    	
    	全通配⽅式:
    	* *..*.*(..)
    
    • 改变代理⽅式的配置
      在前⾯我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。
      但是我们都知道,⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种

      • 使⽤aop:config标签配置
      <aop:config proxy-target-class="true">
      
      • 使⽤aop:aspectj-autoproxy标签配置
      <!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP
      的⽀持-->
      <aop:aspectj-autoproxy proxy-target-class="true">
      </aop:aspectj-autoproxy>
      
  • 五种通知类型

    • 前置通知
      • 配置⽅式:aop:before标签
        <!--
        作⽤:
        	⽤于配置前置通知。
        出现位置:
        	它只能出现在aop:aspect标签内部
        属性:
        	method:⽤于指定前置通知的⽅法名称
        	pointcut:⽤于指定切⼊点表达式
        	pointcut-ref:⽤于指定切⼊点表达式的引⽤
        -->
        <aop:before method="printLog" pointcut-ref="pointcut1">
        </aop:before>
        
      • 执⾏时机
        前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
      • 细节
        前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
    • 正常执⾏时通知
      • 配置⽅式

        <!-- 
        作⽤:
        	⽤于配置正常执⾏时通知
        出现位置:
        	它只能出现在aop:aspect标签内部
        属性:
        	method:⽤于指定后置通知的⽅法名称
        	pointcut:⽤于指定切⼊点表达式
        	pointcut-ref:⽤于指定切⼊点表达式的引⽤
        	-->
        <aop:after-returning method="afterReturningPrintLog" pointcutref="pt1">
        </aop:after-returning>
        
    • 异常通知
      • 配置⽅式

        <!--
        作⽤:
        	⽤于配置异常通知。
        出现位置:
        	它只能出现在aop:aspect标签内部
        属性:
        	method:⽤于指定异常通知的⽅法名称
        	pointcut:⽤于指定切⼊点表达式
        	pointcut-ref:⽤于指定切⼊点表达式的引⽤
        -->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1">
        </aop:after-throwing>
        
      • 执⾏时机
        异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。

      • 细节
        异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。

    • 最终通知
      • 配置⽅式
        <!--
        作⽤:
        	⽤于指定最终通知。
        出现位置:
        	它只能出现在aop:aspect标签内部
        属性:
        	method:⽤于指定最终通知的⽅法名称
        	pointcut:⽤于指定切⼊点表达式
        	pointcut-ref:⽤于指定切⼊点表达式的引⽤
        -->
        <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
        
      • 执⾏时机
        最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
      • 细节
        最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
    • 环绕通知
      • 配置⽅式

        <!--
        作⽤:
        	⽤于配置环绕通知。
        出现位置:
        	它只能出现在aop:aspect标签的内部
        属性:
        	method:⽤于指定环绕通知的⽅法名称
        	pointcut:⽤于指定切⼊点表达式
        	pointcut-ref:⽤于指定切⼊点表达式的引⽤
        -->
        <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        
      • 特别说明
        环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。

Spring 声明式事务的⽀持

编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务:通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务

事务回顾
事务的概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。

例如:A——B转帐,对应于如下两条sql语句:

## 转出账户减钱
update account set money=money-100 where name=‘a’;
## 转⼊账户加钱
update account set money=money+100 where name=‘b’;

这两条语句的执⾏,要么全部成功,要么全部不成功。

事务的四⼤特性
  • 原⼦性(Atomicity)
    原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。
    从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
  • ⼀致性(Consistency)
    事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
  • 隔离性(Isolation)
    事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
  • 持久性(Durability)
    持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。
事务的隔离级别

不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题

  • 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
  • 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)
  • 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)

数据库共定义了四种隔离级别:

  • Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼
  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 该机制下会对要update的⾏进⾏加锁
  • Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。
  • Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低

注意:级别依次升⾼,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ
查询当前使⽤的隔离级别: select @@tx_isolation;
设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前
mysql连接会话的,并不是永久改变的)

事务的传播⾏为

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。
A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为

  • PROPAGATION_REQUIRED
    • 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择。
  • PROPAGATION_SUPPORTS
    • ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
  • PROPAGATION_MANDATORY
    • 使⽤当前的事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW
    • 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED
    • 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER
    • 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED
    • 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。
Spring中事务的API

mybatis: sqlSession.commit();
hibernate: session.commit();

PlatformTransactionManager

public interface PlatformTransactionManager {
	/**
	* 获取事务状态信息
	*/
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
	throws TransactionException;
	/**
	* 提交事务
	*/
	void commit(TransactionStatus status) throws TransactionException;
	/**
	* 回滚事务
	*/
	void rollback(TransactionStatus status) throws TransactionException; }

作⽤
此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。在Spring框架中,也为我们内置了⼀些具体策略,例如:DataSourceTransactionManager , HibernateTransactionManager 等等。
DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代理)来将事务控制逻辑织⼊到业务代码

Spring 声明式事务配置
  • xml 配置

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    	<!--定制事务细节,传播⾏为、隔离级别等-->
    	<tx:attributes>
    		<!--⼀般性配置-->
    		<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
    		<!--针对查询的覆盖性配置-->
    		<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
    	</tx:attributes>
    </tx:advice> 
    <aop:config>
    	<!--advice-ref指向增强=横切逻辑+⽅位-->
    	<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.learn.edu.service.impl.TransferServiceImpl.*(..))"/>
    </aop:config>
    
  • 基于XML+注解

    • xml配置

      <!--配置事务管理器-->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
      	<property name="dataSource" ref="dataSource"></property>
      </bean>
      <!--开启spring对注解事务的⽀持-->
      <tx:annotation-driven transaction-manager="transactionManager"/>
      
    • 在接⼝、类或者⽅法上添加@Transactional注解

      @Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
      
  • 基于纯注解
    Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的 <tx:annotation-driven transactionmanager=“transactionManager”/> 配置。
    在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可

    @EnableTransactionManagement//开启spring注解事务的⽀持
    public class SpringConfiguration {
    }
    

    在接⼝、类或者⽅法上添加@Transactional注解

    @Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
    

SpringBean的生命周期图

在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Captain Leo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值