Spring基于AOP事务控制实现原理

2023年 8月 7日 59.7k 0

1.概述

对于一个系统应用而言,使用数据库进行数据存储是必然的,意味着开发过程中事务的使用及控制也是必不可少的,当然事务是数据库层面的知识点并不是Spring框架所提出的。使用JDBC开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SqlSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,所以Spring 就在这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制和声明式事务控制。

  • 编程式事务控制:Spring提供了事务控制的类和方法,使用编码的方式对业务代码进行事务控制,事务控制代码和业务操作代码耦合到了一起,开发中几乎不使用
  • 声明式事务控制: Spring将事务控制的代码封装,对外提供了Xml和注解配置方式,通过配置的方式完成事务的控制,可以达到事务控制与业务操作代码解耦合,开发中推荐使用

2.Spring事务管理和封装

2.1 原生事务控制

在没有框架对事务进行封装之前,我们都是使用底层的原生api来进行事务控制,如JDBC操作数据库控制事务

 // 加载数据库驱动 
 Class.forName("com.mysql.jdbc.Driver");
 // 获取mysql数据库连接
 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
 conn.setAutoCommit(false);
 // 获取statement
 statement = conn.createStatement();
 // 执行sql,返回结果集
 resultSet = statement.executeQuery("xxxx");
 // 提交
 conn.commit();
 // 回滚
 // conn.rollback(); 

这就是原生操作事务的流程,在我们使用Spring框架开发业务系统时也是离不了这样的事务操作的,如果每与数据库交互都需要按上面步骤进行操作,就会显得十分臃肿、重复编码,所以Spring对此进行了封装来提高编程的效率,让事务控制这一过程自动化、透明化,从而做到让开发者专注业务逻辑编码,无需关注事务控制,由Spring框架AOP切面完成即可。

2.2 Spring提供的事务API

Spring基于模版方法设计模式实现了事务控制的封装,核心API和模板类如下:

核心类 解释
平台事务管理器PlatformTransactionManager 是一个接口标准,实现类都具备事务提交、回滚和获得事务对象的功能,不同持久层框架可能会有不同实现方案
事务定义TransactionDefinition 封装事务的隔离级别、传播行为、过期时间等属性信息
事务状态TransactionStatus 存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等

事务管理器—PlatformTransactionManager

PlatformTransactionManager是事务管理器的顶层接口,只规定了事务的基本操作:创建事务,提交事物和回滚事务。

public interface PlatformTransactionManager extends TransactionManager {
​
 // 打开事务
 TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
   throws TransactionException;
​
 // 提交事务
 void commit(TransactionStatus status) throws TransactionException;
​
  // 回滚事务
 void rollback(TransactionStatus status) throws TransactionException;
}

Spring使用模板方法模式提供了一个抽象类AbstractPlatformTransactionManager,规定了事务管理器的基本框架,仅将依赖于具体平台的特性作为抽象方法留给子类实现,如mybatis框架的事务管理器是DatasourceTransactionManagerhibernate框架的事务管理器是HibernateTransactionManager,我曾经见过一个项目服务里的ORM框架同时使用了mybatis,hibernate两个框架,至于为啥?大概是想从hibernate转为mybatis吧....然后有这么一个问题,一个逻辑方法有两个数据库操作,一个是用mybatis实现的,一个是用hibernate实现,这个逻辑方法使用了@Transactional(rollbackFor = Exception.class),但是事务竟然没控制住~前面就是问题的原因所在,mybatishibernate的事务管理器都不是同一个,肯定控制不住事务的。

事务状态—TransactionStatus

存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等。

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
​
 /**
  * 是否有Savepoint Savepoint是当事务回滚时需要恢复的状态
  */
 boolean hasSavepoint();
​
 /**
  * flush()操作和底层数据源有关,并非强制所有数据源都要支持
  */
 @Override
 void flush();
​
}

还从父接口TransactionExecution,SavepointManager中继承了其他方法

/**
  * 是否是新事务(或是其他事务的一部分)
  */
 boolean isNewTransaction();
​
 /**
  * 设置rollback-only 表示之后需要回滚
  */
 void setRollbackOnly();
​
 /**
  * 是否rollback-only
  */
 boolean isRollbackOnly();
​
 /**
  * 判断该事务已经完成
  */
 boolean isCompleted();
 
 
 /**
  * 创建一个Savepoint
  */
 Object createSavepoint() throws TransactionException;
​
 /**
  * 回滚到指定Savepoint
  */
 void rollbackToSavepoint(Object savepoint) throws TransactionException;
​
 /**
  * 释放Savepoint 当事务完成后,事务管理器基本上自动释放该事务所有的savepoint
  */
 void releaseSavepoint(Object savepoint) throws TransactionException;

事务属性的定义—TransactionDefinition

TransactionDefinition封装事务的隔离级别、传播行为、过期时间等属性信息

 /**
  * 返回事务的传播级别
  */
 default int getPropagationBehavior() {
  return PROPAGATION_REQUIRED;
 }
​
 /**
  * 返回事务的隔离级别
  */
 default int getIsolationLevel() {
  return ISOLATION_DEFAULT;
 }
​
 /**
  * 事务超时时间
  */
 default int getTimeout() {
  return TIMEOUT_DEFAULT;
 }
​
 /**
  * 是否为只读事务(只读事务在处理上能有一些优化)
  */
 default boolean isReadOnly() {
  return false;
 }
​
 /**
  * 返回事务的名称
  */
 @Nullable
 default String getName() {
  return null;
 }
​
​
 /**
  * 默认的事务配置
  */
 static TransactionDefinition withDefaults() {
  return StaticTransactionDefinition.INSTANCE;
 }

2.3 Spring编程式事务实现

基于上面底层的API,开发者可以在代码中手动的管理事务的开启、提交、回滚等操作来完成编程式事务控制。在spring项目中可以使用TransactionTemplateTransactionCallback进行实现手动控制事务。

   //设置事务的各种属性;可以猜测TransactionTemplate应该是实现了TransactionDefinition
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        transactionTemplate.setTimeout(30000);
        
        //执行事务 将业务逻辑封装在TransactionCallback中
        transactionTemplate.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                    //....   业务代码
            }
        });

但是我们在开发过程中一般不使用编程式事务控制,因为比较繁琐不够优雅,一般是使用声明式进行事务控制,所以接下来就重点讲讲声明式事务。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:github.com/plasticene/…

Gitee地址:gitee.com/plasticene3…

微信公众号:Shepherd进阶笔记

交流探讨qun:Shepherd_126

3.声明式事务

3.1 示例

@Transactional是Spring中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚,直接来看看我们添加用户和角色的示例代码:

​
  @Transactional(rollbackFor = Exception.class)
  public void addUser(UserParam param) {
      String username = param.getUsername();
      checkUsernameUnique(username);
      User user = PtcBeanUtils.copy(param, User.class);
      userDAO.insert(user);
      if (!CollectionUtils.isEmpty(param.getRoleIds())) {
          userRoleService.addUserRole(user.getId(), param.getRoleIds());
      }
  }

可以看出在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。

3.2 实现原理

@EnableTransactionManagement说起,该注解开启注解声明式事务,所以我们就先来看看其定义:

 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
​
 /**
  * 用来表示默认使用JDK Dynamic Proxy还是CGLIB Proxy
  */
 boolean proxyTargetClass() default false;
​
 /**
  * 表示以Proxy-based方式实现AOP还是以Weaving-based方式实现AOP
  */
 AdviceMode mode() default AdviceMode.PROXY;
​
 /**
  * 顺序
  */
 int order() default Ordered.LOWEST_PRECEDENCE;
​
}

可以看出,和注解@EnableAspectJAutoProxy开启aop代理差不多,核心逻辑:@Import(TransactionManagementConfigurationSelector.class)TransactionManangementConfigurationSelector主要是往Spring容器中注入相关bean,核心逻辑如下:

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector {
​
  /**
   * 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[] {AutoProxyRegistrar.class.getName(),
            ProxyTransactionManagementConfiguration.class.getName()};
      case ASPECTJ:
        return new String[] {determineTransactionAspectClass()};
      default:
        return null;
    }
  }
} 

3.2.1 如何生成代理类

AutoProxyRegistrar通过调用AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);向容器中注册AbstractAdvisorAutoProxyCreator,这个类在之前总结的 Spring切面编程实现原理一文中重点解析过,是生成AOP代理类的核心实现所在。

UserService实现类中使用了@Transational来进行数据库事务控制,AuthService中不涉及到数据库事务处理,从图中可知UserService是被CGLIB动态代理生成的代理类,而AuthService是原生类,这就是AbstractAdvisorAutoProxyCreator实现的,

#getAdvicesAndAdvisorsForBean()会判断bean是否有advisor,有的话就通过动态代理生成代理对象注入到Spring容器中,这就是前面UserService代理对象的由来。

  protected Object[] getAdvicesAndAdvisorsForBean(
      Class beanClass, String beanName, @Nullable TargetSource targetSource) {
​
    List advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
    }
    return advisors.toArray();
  }

#findEligibleAdvisors()顾名思义就是找到符合条件的advisor

  protected List findEligibleAdvisors(Class beanClass, String beanName) {
    List candidateAdvisors = findCandidateAdvisors();
    List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
  }

#findCandidateAdvisors()查找所有候选的advisor

  @Override
  protected List findCandidateAdvisors() {
    // Add all the Spring advisors found according to superclass rules.
    List advisors = super.findCandidateAdvisors();
    // Build Advisors for all AspectJ aspects in the bean factory.
    if (this.aspectJAdvisorsBuilder != null) {
      advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
  }

super.findCandidateAdvisors()就是获取spring内部规则的advisor,比如说事务控制的advisor:BeanFactoryTransactionAttributeSourceAdvisor

this.aspectJAdvisorsBuilder.buildAspectJAdvisors()是解析使用了@Aspect的切面类,根据切点表达式生成advisor。

#findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName)调用AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);筛选出能匹配当前bean的advisor。

AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass)会调用#canApply()方法:

  public static boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions) {
    if (advisor instanceof IntroductionAdvisor) {
      return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
    }
    else if (advisor instanceof PointcutAdvisor) {
      PointcutAdvisor pca = (PointcutAdvisor) advisor;
      return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
      // It doesn't have a pointcut so we assume it applies.
      return true;
    }
  }

这里判断如果是PointcutAdvisor类型,就会调用canApply(pca.getPointcut(), targetClass, hasIntroductions);,上面提到的事务advisor:BeanFactoryTransactionAttributeSourceAdvisor正好符合。执行BeanFactoryTransactionAttributeSourceAdvisorTransactionAttributeSourcePointcut对象的matches()方法来进行是否匹配判断,然后根据当前bean的所有method遍历执行判断使用有@Transational注解,来到AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute()方法

  @Nullable
  protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
​
    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
​
    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
      return txAttr;
    }
​
    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }
​
    if (specificMethod != method) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
        return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
      }
    }
​
    return null;
  }

最终来到SpringTransactionAnnotationParser#parseTransactionAnnotation()

​
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
    //这里就是分析Method是否被@Transactional注解标注,有的话,不用说BeanFactoryTransactionAttributeSourceAdvisor适配当前bean,进行代理,并且注入切点
    //BeanFactoryTransactionAttributeSourceAdvisor
   AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
   if (attributes != null) {
      return parseTransactionAnnotation(attributes);
   }
   else {
      return null;
   }
}

上面就是判断是否需要根据@Transactional进行代理对象创建的判断过程。@Transactional的作用就是标识方法需要被代理,同时携带事务管理需要的属性信息。

3.2.2 如何进行事务控制

在之前Spring切面编程实现原理一文中我碍于文章篇幅只分析了JDK代理的实现方式,但在 SpringBoot 2.x AOP中会默认使用Cglib来实现,所以今天就来分析一下CGLIB这种方式。

Spring的CGLIB方式生存代理对象是靠ObjenesisCglibAopProxy完成的,ObjenesisCglibAopProxy继承自CglibAopProxy,调用方法#createProxyClassAndInstance()得到基于Cglib动态代理的对象。最终的代理对象的代理方法DynamicAdvisedInterceptor#intercept()方法

    @Override
    @Nullable
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      Object oldProxy = null;
      boolean setProxyContext = false;
      Object target = null;
      TargetSource targetSource = this.advised.getTargetSource();
      try {
        if (this.advised.exposeProxy) {
          // Make invocation available if necessary.
          oldProxy = AopContext.setCurrentProxy(proxy);
          setProxyContext = true;
        }
        // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
        target = targetSource.getTarget();
        Class targetClass = (target != null ? target.getClass() : null);
        List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // Check whether we only have one InvokerInterceptor: that is,
        // no real advice, but just reflective invocation of the target.
        if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
          // We can skip creating a MethodInvocation: just invoke the target directly.
          // Note that the final invoker must be an InvokerInterceptor, so we know
          // it does nothing but a reflective operation on the target, and no hot
          // swapping or fancy proxying.
          Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
          try {
            retVal = methodProxy.invoke(target, argsToUse);
          }
          catch (CodeGenerationException ex) {
            CglibMethodInvocation.logFastClassGenerationFailure(method);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
          }
        }
        else {
          // We need to create a method invocation...
          retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
      }
      finally {
        if (target != null && !targetSource.isStatic()) {
          targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
          // Restore old proxy.
          AopContext.setCurrentProxy(oldProxy);
        }
      }
    }

这和以JDK实现的动态代理JdkDynamicAopProxy实现了InvocationHandler执行invoke()来进行逻辑增强套路是一样的。

通过分析 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)返回的是TransactionInterceptor,然后来到new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(),最终调用TransactionInterceptor#invoke()方法

  @Override
  @Nullable
  public Object invoke(MethodInvocation invocation) throws Throwable {
    // Work out the target class: may be {@code null}.
    // The TransactionAttributeSource should be passed the target class
    // as well as the method, which may be from an interface.
    Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
​
    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
  }

#invokeWithinTransaction()就是通过切面实现事务控制的核心逻辑所在:

 @Nullable
 protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
   final InvocationCallback invocation) throws Throwable {
​
  
  TransactionAttributeSource tas = getTransactionAttributeSource();
  final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  final TransactionManager tm = determineTransactionManager(txAttr);
​
  //省略部分代码
        
        //获取事物管理器
  PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
  final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
​
  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
   // 打开事务(内部就是getTransactionStatus的过程)
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
​
   Object retVal;
   try {
    // 执行业务逻辑 invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
    // 异常回滚
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
   }
   finally {
    cleanupTransactionInfo(txInfo);
   }
​
   //省略部分代码
            
            //提交事物
   commitTransactionAfterReturning(txInfo);
   return retVal;
  }

4.总结

行文至此,Spring基于AOP自动完成事务控制的逻辑分析就完结了。Spring的声明式事务注解开发非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。但是在日常开发中却经常出现事务失效的情况,所以了解Spring事务控制实现还是很有必要的,同时还可以加深对Spring AOP应用的认识。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论