美文网首页
SpingBoot Transaction 核心源码分析

SpingBoot Transaction 核心源码分析

作者: 丑人林宗己 | 来源:发表于2019-12-24 13:49 被阅读0次

    起因

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

    目标

    • 了解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.<Integer>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<Map<Object, Object>> resources, 该线程副本的内部数据结构Map<Object, Object>,其中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 核心源码分析

          本文链接:https://www.haomeiwen.com/subject/baltoctx.html