SpingBoot Transaction 核心源码分析

起因

之前测试反馈过一个问题,钱没扣成功,但是却生成了扣钱的记录,最终解决了,但是没有去深究。

目标

  • 了解Spring Boot Transaction的核心技术点
  • 了解事务相关的钩子,方便后续日常开发
  • 了解一下多数据源的实现原理

准备工作

在https://start.spring.io/ 或者基于IDEA快速生成一个MVC的项目,便于调试,包括Web, Mybatis, Test

前言

1、本次探究基于Spring Boot 5.0.2,其次主要看基于@Transactional的声明式的事务。
2、Spring Transaction的实现方案为Spring AOP,如果对于Spring AOP不是很了解,建议多花一些时间研究一下
3、基于Spring Boot自动配置原理,核心在于查找xxxAutoConfiguration自动配置类 --> TransactionAutoConfiguration

在阅读源码前可以先思考,猜测Spring将会如何利用AOP理念优雅的实现事务,比如:

1、必须有一个类,或者一些实现类,来共同完成判断是否要给方法增加事务,事务属性等等的工作
2、基于Spring Boot默认使用CGLIB,那么一个有实现MethodInterceptor的事务拦截器
3、猜测是基于ThreadLocal实现的默认的事务传播属性
4、……

核心源码分析

翻阅刚刚说的TransactionAutoConfiguration -> EnableTransactionManagement -> TransactionManagementConfigurationSelector
-> AutoProxyRegistrar, ProxyTransactionManagementConfiguration 这两个类非常核心

AutoProxyRegistrar

该类做了的事非常核心,翻阅源码,重点关注

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
-> registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);

创建了InfrastructureAdvisorAutoProxyCreator,紧见其名,便可猜测它的核心能力,与创建动态代理对象相关

其次关注它的父类,因为在对象实例到Spring容器的整个过程会有很多钩子函数,比如很常见的BeanPostProcessor,父类实现了该接口,看AbstractAutoProxyCreator,它的postProcessAfterInitialization的核心能力在于,当Bean被放入容器初始化完成后进行一些处理,比如是否需要生成代理对象进行包装

@Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

到这里,很明确,所有的对象都会经过该层进行处理,但是前提是需要对象是放在Spring容器的对象,如果不是,则不会被处理。所以一般只有诸如@Service, @Compent等等的方式才会被处理,记住wrapIfNecessary方法,稍后回来继续分析

ProxyTransactionManagementConfiguration

此类非常核心,创建的几个Bean都是实现Spring Transaction最核心的部分内容

public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

    // 事务属性增强器
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
            TransactionAttributeSource transactionAttributeSource,
            TransactionInterceptor transactionInterceptor) {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource);
        advisor.setAdvice(transactionInterceptor);
        if (this.enableTx != null) {
            advisor.setOrder(this.enableTx.getNumber("order"));
        }
        return advisor;
    }

    // 事务属性  
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    // 事务拦截器
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionInterceptor transactionInterceptor(
            TransactionAttributeSource transactionAttributeSource) {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource);
        if (this.txManager != null) {
            interceptor.setTransactionManager(this.txManager);
        }
        return interceptor;
    }
}

BeanFactoryTransactionAttributeSourceAdvisor事务增加器,请关注它的父类,其次关注它的属性,构造的时候包括一个AnnotationTransactionAttributeSource,以及一个TransactionInterceptor

它实现了一个PointcutAdvisor接口,而最终该接口返回的对象是TransactionAttributeSourcePointcut,从接口名称可以得出该方法与AOP的切面相关,其次它也实现了接口MethodMatcher,匹配规则,入参是方法,以及类对象,而它的实现如下

@Override
    public boolean matches(Method method, Class targetClass) {
        TransactionAttributeSource tas = getTransactionAttributeSource();
        return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
    }

注意tas其实是AnnotationTransactionAttributeSource,而该类扩展了AbstractFallbackTransactionAttributeSource,而该父类的getTransactionAttribute方法包括了

@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) {
        // ....
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
    }

往下继续看会翻到的代码如下:

protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(element);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }
// annotationParsers在对象初始化时默认如下
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());

// SpringTransactionAnnotationParser 的核心
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
    AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
            element, Transactional.class, false, false);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    }
    else {
        return null;
    }
}

至此,终于看到熟悉的@Transactional,也就是说,在这里对方法上的注解进行分析,拆分成一个事务属性对象,至此可以看到BeanFactoryTransactionAttributeSourceAdvisor该类的核心能力就在于将对象的方法上那些写了@Transactional给找出来并进行分析,而且该对象还内置了TransactionInterceptor,以此相互串联起来,如果这些对象需要增强,那么就通过动态代理技术生成一个新的代理对象。那么什么时候开始找@Transactional,以此确定TransactionInterceptor,始于前文提到的wrapIfNecessary方法

AbstractAutoProxyCreator

wrapIfNecessary 的核心在于判断当前对象需要进行相应的增强方式,并找到它们所对应的拦截器

// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
}

getAdvicesAndAdvisorsForBean
 -> findEligibleAdvisors
  -> findAdvisorsThatCanApply
  -> AopUtils.findAdvisorsThatCanApply
  -> 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) { // BeanFactoryTransactionAttributeSourceAdvisor 扩展该接口
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        }
        else {
            // It doesn't have a pointcut so we assume it applies.
            return true;
        }
    }

public static boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) {
        // ....
        MethodMatcher methodMatcher = pc.getMethodMatcher();
        // ....
        for (Class clazz : classes) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                if (introductionAwareMethodMatcher != null ?
                        introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                        methodMatcher.matches(method, targetClass)) { // 查阅 TransactionAttributeSourcePointcut
                    return true;
                }
            }
        }

        return false;
    }

查阅 TransactionAttributeSourcePointcut

@Override
public boolean matches(Method method, Class targetClass) {
   TransactionAttributeSource tas = getTransactionAttributeSource();
   return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

至此,终于跟前文提到的解析过程合理的相关联起来,处理的最核心在于使用BeanPostProcessor,而要注意的是在查找过程最终返回的是Advisor,而我们都知道CGLIB创建的时候是MethodInterceptor,但是,MethodInterceptor却是扩展自Advisor

所以取到Advisor对象列表,就可能是实现了MethodInterceptor的对象列表,翻阅生成代理对象时也确实会判断

@Override
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    // ....
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {  
        // So well-known it doesn't even need an adapter.
        return new DefaultPointcutAdvisor(advice);// 将对象进行包装
    }
    // ...
}

至此,从事务方法是如何被一步步扫描,解析,并通过CGLIB生成代理对象来实现切面事务的方法的完整过程算是大致清晰了,重点关注的核心对象的创建类ProxyTransactionManagementConfiguration

TransactionInterceptor

事务拦截器的真正处理的地方。
而在了解事务处理机制之前,先梳理一下已有的知识点,包括

  • 在配置文件中配置spring.datasource.xxx等信息
  • JDBC原生的事务接口,包括对象DataSource, Connection等等
  • 在XML配置文件时代常见DataSourceTransactionManager切面配置(事务管理器)
  • ...

本次翻阅源码主要想要了解的点,如下:

  • Spring Boot在事务上的一些配置类
  • 事务提交/回滚等过程涉及的一些核心异常
  • 事务提交/回滚等过程提供的一些钩子函数
  • 多数据源的设计方案原理
  • ....

事务切面的核心程序如下:

@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);
}

protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
        final InvocationCallback invocation) throws Throwable {
    // ....
    // If the transaction attribute is null, the method is non-transactional.
    TransactionAttributeSource tas = getTransactionAttributeSource();
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); // 取到事务属性
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);  // 事务管理器
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);  
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); // 创建事务信息
        Object retVal;
        try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);  // 异常后事务处理
            throw ex;
        }
        finally {
             cleanupTransactionInfo(txInfo);  // 事务清理
        }
              // ...
        commitTransactionAfterReturning(txInfo); // 事务提交
        return retVal;
    }
// ...
}

spring.datasource.xxx很容易找到DataSourceProperties, 而通过Debug也很容易发现事务管理器的对象是DataSourceTransactionManager, 而二者的核心配置类是DataSourceTransactionManagerAutoConfiguration

Hikari也是作为Spring Boot新默认的一个数据源解决方案。

createTransactionIfNecessary入口看事务的创建过程

createTransactionIfNecessary 
 -> tm.getTransaction(txAttr)
 -> doGetTransaction()
 -> conHolder = TransactionSynchronizationManager.getResource(obtainDataSource())

最后一步是非常核心的,获取数据源,并从资源池中获取连接
obtainDataSource()的源码没什么秘密,无非就是一个返回一个数据源对象,那么多数据源要如何设计?看看Spring提供的多数据源方案AbstractRoutingDataSource,改接口是2.0.1版本新加入的,而且可以看到其实接口AbstractRoutingDataSource也并没有多少秘密,那么多数据源如何实现?

不能改变Spring Transaction原来的程序,那么切换数据源的程序该放在哪里呢?答案是放在数据源对象本身,因为只需要确保,每次需要获取一个数据库连接来完成操作时,从某一个返回的数据源中获取一个即可,所以AbstractRoutingDataSource的方案是提供数据源切换的一种方案

TransactionSynchronizationManager.getResource的源码似乎也无甚秘密可言,一步步跟进去发现,最终的解决方案是
ThreadLocal> resources, 该线程副本的内部数据结构Map,其中Key是数据源,Value是该数据源的一个连接,而这也正解决了一个请求包含多个数据源的处理思路,但是一个请求多个事务需要提交的时候会比较复杂。

会看到如果是第一次来查找资源,会找不到,所以接下来往下看,源码DataSourceTransactionManager, 看该类的几个核心方法-

  • doBegin(), 从数据源中取出连接并放入缓存,将连接设置为非默认提交,设置超时时间等等,并将该数据源与连接放到线程副本中
  • doRollback()
  • doCommit()

rollback的前提是异常条件为:txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex), 否则则进行commit操作,而这部分异常在于@Transactional#rollbackFor,而默认逻辑是 (ex instanceof RuntimeException || ex instanceof Error);

这部分代码写的都非常清晰,没有人任何难懂的操作,其次翻阅AbstractPlatformTransactionManager,看该类的几个核心方法

  • triggerAfterCommit
  • triggerAfterCompletion
  • triggerBeforeCompletion
  • triggerBeforeCommit
  • triggerFlush
    而这部分方法的逻辑在于接口TransactionSynchronization,俗称钩子函数,常见用法参考如下:
// 事务管理
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){

    @Override
    public void afterCompletion(int status) {
        
        if(TransactionSynchronization.STATUS_COMMITTED == status) {
            // ....
        }
    }
});

总结

至此,整个Spring Boot Transaction算是梳理了一遍,至少把自己想要探究的一些点捋出来了,脉络看起来也是非常的清晰。
最核心在于要了解BeanPostProcessor机制,其次要了解AOP机制,包括AOP技术点,Advisor, MethodInterceptor等等,其次要还要了解关于JDBC 提供的事务相关的接口,Spring Transaction并未对此做任何改变,都是基于JDBC规范进行的二次封装与设计。

前文提到出现的问题,在于发现程序进入Service时的方法没有@Transactional,而是在其内部调用另外一个方法时采取调用另外一个有@Transactional的方法,所以很不辛,事务没有生效。原因跟动态代理生成的对象是有关系的,采用如下代码记录生成的动态代理的类文件,通过JD-GUI反编译看代码

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/usr/local/test/class");

是否开启事务,在于子类,即代理对象,父类只是普通对象,不具备事务的处理能力。

翻看Spring的源码经常有种其实就这样的感觉……

你可能感兴趣的:(SpingBoot Transaction 核心源码分析)