老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(一)
老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(一)
前言
前面的系列文章已经大概讲解了Spring Aop的实现,从AspectJ开始,到Spring的实现,再到Spring的实现细节以及JDK动态代理和CGLIB动态代理的一些例子。
那么Aop除了日常的一些用法外,在Spring框架层面有没有经典的应用呢?答案是有的,就是 Spring 事务。Spring 事务的实现是完全基于Spring Aop来实现的,也就是说事务也是在业务逻辑方法之前或者之后帮我们完成了事务的相关操作。既然道理是这么个道理,那Spring 事务使用的是什么类型的切面以及如何切入的呢?提前剧透一下具体的切面类型Spring使用的是Around advice,Spring 事务选择的是在业务方法逻辑之后执行相应的事务操作。话不多说,搞个例子耍耍。
Spring事务的简单使用
代码样例
环境:mysql:5.7 spring:5.2.2 jdk:1.8
这里代码我是基于
XML实现的,注解更加简单,原理都是一样的。什么?你看
xml不爽,非要用注解?我偏不,
xml才是灵魂。
首先搞个实体类
User
/** * @author Codegitz * @date 2022/2/18 10:29 **/ public class User { private String id; private String name; private int age; private String sex; // 省略getter setter }
再搞个业务类接口
UserService,有个
save()方法,类上添加了
@Transactional注解。这个就很经典了,注解到底是加在接口上还是加在实现类上,这两种有什么区别?各位看官可以思考一下,单体应用好像没什么区别,但是如果是微服务呢?如果是通过dubbo提供的服务,依赖了一组dubbo-api,那么这时候注解加在接口上还是实现类上就会有比较明显的区别,可以分为服务调用方的事务实现和服务实现方的事务实现。
/** * @author Codegitz * @date 2022/2/18 10:28 **/ @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public interface UserService { /** * 保存用户信息 * @param user 用户信息 */ void save(User user) throws Exception; }
UserServiceImpl类实现接口,这里有个抛出异常的代码,如果在运行时抛出异常,那么就会发生事务回滚,此时数据是不会被保存到数据库的。
/** * @author Codegitz * @date 2022/2/18 10:31 **/ public class UserServiceImpl implements UserService { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource){ this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public void save(User user) throws Exception{ jdbcTemplate.update("insert into user(name,age,sex) values(?,?,?)" ,new Object[]{user.getName(),user.getAge(),user.getSex()} ,new int[]{Types.VARCHAR,Types.INTEGER,Types.VARCHAR}); // 事务测试,如果抛出异常则数据实际上不会被保存到数据库中 // throw new RuntimeException("Throwing exceptions manually..."); } }
业务代码编写完事了,接下来搞个
xml配置文件,
xml配置文件主要干了以下几件事:
- 开启事务支持
- 配置数据源
- 配置业务类UserService
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/transaction"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="initialSize" value="1"/> <property name="maxActive" value="300"/> <property name="maxIdle" value="2"/> <property name="minIdle" value="1"/> </bean> <bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
那接下来万事俱备只欠东风,写个Application跑一跑
/** * @author Codegitz * @date 2022/2/18 11:02 **/ public class Application { public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); User user = new User(); user.setName("codegitz"); user.setAge(25); user.setSex("man"); // insert a record userService.save(user); } }
测试过程
首先测试正常不抛出异常的代码
首先确认数据库里是没有数据的。执行代码后再查询,可以看到数据已经插入。
接下来测试抛出异常的代码,把注释了的抛出异常代码打开。
同样先确认数据库里没有数据,执行代码抛出异常后,再检查数据库,数据没有插入,毫无疑问事务进行了回滚。
Spring事务的实现原理
大白话原理
上一节我们已经看到了事务的操作,结果符合我们的预期。那么这一节我们来了解一下Spring事务实现的框架,并不进入深入的分析,力求先把握其脉络,再深入其细节。通过上面简单的代码,我们可以发现。只需要开启Spring的事务支持,然后在需要事务的方法上添加
@Transactional注解就能在方法抛出异常的时候进行回滚,那么Spring是如何进行具体操作的呢?
在文章的开头我们说了,Spring事务是利用Spring Aop来实现的,那么很显然,既然是使用了Aop,就绕不开Aop的几个概念。我们可以类比一个横切逻辑的实现,敲黑板,重点来了。
首先我们业务代码切面会定义一个
pointcut
,通过这个可以挑选出我们感兴趣的方法。那在事务的实现里,我们对什么感兴趣呢?毫无疑问,我们对@Transactional
标注的类和方法感兴趣,所以我们给方法打上@Transactional
类似于Aop的定义个pointcut
。在定义了
pointcut
后,接下来就需要定义切面的类型和切面执行的逻辑。在我们的写切面时,会有@Before
、@After
以及@Around
等通知类型,在文章开头已经说了,Spring 事务用的通知类型是@Around
,那接下来就剩一个,切面的逻辑是什么,需要执行什么。如果看过前面的文章就会知道,无论是JDK动态代理还是CGLIB动态代理,最终在调用的时候都会转发到InvocationHandler#invoke()
或者MethodInterceptor#invoke()
方法上,所以事务的重要逻辑肯定是实现这里的某个invoke()
方法。那么Spring 事务是哪个类实现了呢?这里直接揭晓谜底,这个重要的类就是TransactionInterceptor
,该类里面的invoke()
方法就是我们实现核心事务逻辑的地方。在获取
TransactionInterceptor
之后,只需要把TransactionInterceptor
传入生成动态代理的方法里,那这个业务类就拥有了事务的能力,这里对应的业务类是UserService
,最终我们拿到的是这个类的代理类,这个代理类替我们完成事务的操作。看到这里机智的朋友已经想到了事务失效的几个场景,例如@Transactional
加在私有方法上不生效以及@Transactional
加在private
方法上不生效等等,多半是动态代理的限制。
到这里思路清晰了没?不知道我在说什么?那先去把之前的Aop文章回忆一下。回忆了还是看不懂?那不怪你,我也是乱写的。
核心类
接下来看一下框架的核心类,混个眼熟,下篇文章老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(二)会进行分析。主要就是以下三个类,这三个类撑起了Spring事务的体系。
这里只是混个眼熟,心里可以默念三遍每个类的名字,心里留个印象。
BeanFactoryTransactionAttributeSourceAdvisor
首先摘取类上的注释,简单来说这个类就是找出被事务标记的类或者方法。
Advisor driven by a {@link TransactionAttributeSource}, used to include a transaction advice bean for methods that are transactional.
来看一下类继承图,可以看到这就是个
Advisor,在代理类执行目标方法的时候,会调用其内部的
Advice也就是
TransactionInterceptor来执行切面逻辑。
AnnotationTransactionAttributeSource
首先摘取类上的注释
Implementation of the {@link org.springframework.transaction.interceptor.TransactionAttributeSource} interface for working with transaction metadata in JDK 1.5+ annotation format. <p>This class reads Spring's JDK 1.5+ {@link Transactional} annotation and exposes corresponding transaction attributes to Spring's transaction infrastructure. Also supports JTA 1.2's {@link javax.transaction.Transactional} and EJB3's {@link javax.ejb.TransactionAttribute} annotation (if present). This class may also serve as base class for a custom TransactionAttributeSource, or get customized through {@link TransactionAnnotationParser} strategies.
这个类是用来读取
@Transactional注解的属性,并且暴露对应的事务属性给Spring事务处理的基础类。
TransactionInterceptor
首先摘取类上的注释
* AOP Alliance MethodInterceptor for declarative transaction * management using the common Spring transaction infrastructure * ({@link org.springframework.transaction.PlatformTransactionManager}/ * {@link org.springframework.transaction.ReactiveTransactionManager}). * * <p>Derives from the {@link TransactionAspectSupport} class which * contains the integration with Spring's underlying transaction API. * TransactionInterceptor simply calls the relevant superclass methods * such as {@link #invokeWithinTransaction} in the correct order. * * <p>TransactionInterceptors are thread-safe.
这个就比较复杂了,这里会获取相应的
TransactionManager,依赖于底层的数据库实现事务的操作,这里的大部分操作都委托给了
TransactionAspectSupport类去实现。
来看下类继承图,是不是显而易见,这是个
Advice,就是要被调用的事务逻辑代码。
总结
简单总结一下,这篇文章首先写了一个常用的事务样例代码,并且简单测试了事务的效果。随后我类比Aop分析了Spring 事务的代码实现逻辑,试图讲清楚事务是怎么利用Aop去实现的,并且类比了Aop的概念,值得品味,虽然简单,但是十分经典。最后简单贴了三个重要的类,分别为
BeanFactoryTransactionAttributeSourceAdvisor、
AnnotationTransactionAttributeSource和
TransactionInterceptor,这三个类撑起了Spring事务的体系,为什么一再强调,因为后面的分析都是围绕这哥仨展开,最好做到未见其人先闻其名。
这篇比较简单,只是尝试给大家脑子里留下个大概的脉络,避免一上来就怼实现怼分析,那样一篇就劝退。我佛慈悲,这次做个好心人,简单点来。
水平有限,如有错漏,还请指出。
如果有人看到这里,那在这里老话重提。与君共勉,路漫漫其修远兮,吾将上下而求索。
- 老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(三)
- 老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(二)
- AOP基本概念、AOP底层实现原理、AOP经典应用【事务管理、异常日志处理、方法审计】
- spring源码分析之——spring 事务管理实现方式 (不太清晰,不明白aop会看不懂)
- Spring AOP 实现原理与 CGLIB 应用
- spring 手动实现aop管理事务
- Spring源码分析----AOP概念(Advice,Pointcut,Advisor)和AOP的设计与实现
- spring aop 入门经典的 和 aop实现方式的几篇文章
- Spring实现事务源码分析
- 对Spring事务管理实现技术的分析
- [AOP系列]Autofac+Castle实现AOP事务
- 对Spring AOP框架实现的结构分析(1)
- Spring AOP 实现原理与 CGLIB 应用
- 利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)
- Spring4笔记9--Spring的事务管理(AOP应用的例子)
- springAOP 实现事务的管理
- Spring AOP实现源码分析(三)
- 基于Spring源码分析AOP的实现机制
- 基于Spring源码分析AOP的实现机制
- Spring AOP 实现原理与 CGLIB 应用--转