书接上回,上篇写到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: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的生命周期图