·

老生常谈系列之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来执行切面逻辑。

1645188263175

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事务处理的基础类。

1645189914763

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,就是要被调用的事务逻辑代码。

1645190344339

总结

简单总结一下,这篇文章首先写了一个常用的事务样例代码,并且简单测试了事务的效果。随后我类比Aop分析了Spring 事务的代码实现逻辑,试图讲清楚事务是怎么利用Aop去实现的,并且类比了Aop的概念,值得品味,虽然简单,但是十分经典。最后简单贴了三个重要的类,分别为BeanFactoryTransactionAttributeSourceAdvisorAnnotationTransactionAttributeSourceTransactionInterceptor,这三个类撑起了Spring事务的体系,为什么一再强调,因为后面的分析都是围绕这哥仨展开,最好做到未见其人先闻其名。

这篇比较简单,只是尝试给大家脑子里留下个大概的脉络,避免一上来就怼实现怼分析,那样一篇就劝退。我佛慈悲,这次做个好心人,简单点来。

水平有限,如有错漏,还请指出。

如果有人看到这里,那在这里老话重提。与君共勉,路漫漫其修远兮,吾将上下而求索。

posted @ 2022-02-18 21:58  Codegitz  阅读(157)  评论(0编辑  收藏  举报