Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

1.spring声明式事务概述


事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性。spring支持编程式事务管理和声明式事务管理两种方式。


首先我们来看看spring框架的事物抽象。Spring的事务策略由TransactionManager接口定义,PlatformTransactionManager接口和ReactiveTransactionManager接口继承了TransactionManager接口。我们的程序大多用的都是PlatformTransactionManager接口。


Spring 5.0之后引入了reactive web框架webflux,与webflux平级的就是webmvc,webflux是一个完全的响应式并且非阻塞的web框架,因此spring 5.2之后Spring还为响应式web框架提供了事务管理抽象,即ReactiveTransactionManager接口。我们下面主要讲的是PlatformTransactionManager事务管理抽象。


下面是PlatformTransactionManager接口的源码:


public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    void commit(TransactionStatus var1) throws TransactionException;
    void rollback(TransactionStatus var1) throws TransactionException;
}


  • getTransaction方法通过传入一个TransactionDefinition类型的参数,来获取一个TransactionStatus对象,如果当前的调用堆栈里已经存在一个匹配的事务,TransactionStatus代表的就是这个已存在的事务,否则TransactionStatus代表一个新的事务。

  • TransactionStatus接口为事务代码提供了一些控制事务执行和查询事务状态的方法。

  • TransactionDefinition是一个接口,该接口里面有一些默认方法,这些默认方法的返回值是声明一个事务的必要属性,比如getPropagationBehavior()、getIsolationLevel()、getTimeout()、isReadOnly()。getPropagationBehavior方法指获取事务的传播行为,getIsolationLevel方法指获取事务的隔离级别,getTimeout方法指获取事务的超时时间,isReadOnly方法指是否为只读事务,即只有读操作而没有写操作的事务。由于TransactionDefinition是一个接口,所以需要实现才能被使用,实现类可以覆盖接口的默认方法,也可以不覆盖,如果不覆盖则使用默认值。

  • commit方法则用于提交事务,rollback方法用于回滚事务。



声明式事务一般使用@Transactional注解,并且使用@EnableTransactionManagement开启事务管理就足够了,但接下来我们讲的是其背后的工作原理。


声明式事务是建立在AOP之上的,首先我们的应用程序会通过XML的方式或者注解的方式提供元数据,AOP与事务元数据结合产生一个代理。当执行目标方法时拦截器TransactionInterceptor会对目标方法进行拦截,然后在目标方法的前后调用代理。其本质是在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。


拦截器TransactionInterceptor通过检查方法返回类型来检测是哪种事务管理风格。如果返回的是响应式类型(例如Publisher)的方法则符合响应式事务管理的条件,其他返回类型包括void则使用PlatformTransactionManager。


@Transactional注解是基于注解的声明式事务,当然基于XMl配置的也可以,但因为是在Spring Boot应用中使用,所以一律使用基于注解的声明式事务。


@Transactional可以作用于接口定义上、接口方法上、类定义上和类的公共方法上,如果作用于私有方法或者包可见的方法上,虽然不会引发错误,但是并不会激活事务的一些行为。另外,将@Transactional作用于类上要比作用于接口上要更好,下面我来说明下原因。

Spring AOP框架中有两种模式,分别是基于代理和基于AspectJ,而基于代理又分为两种,一种是基于接口的,一种是基于类的,如果是基于类的代理或是基于AspectJ,则将@Transactional作用于接口上不会起到任何作用。


上面说到了事务是基于AOP的,那么如何让事务支持多种模式呢?@EnableTransactionManagement注解提供了相关的支持,@EnableTransactionManagement注解的源码如下:


public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default 2147483647;
}


可以看到,事务管理默认模式为基于代理,即mode=AdviceMode.PROXY,且默认的代理方式为基于接口的,因为proxyTargetClass=false。


下面来看一下@Transactional注解的属性有哪些:

属性 类型 描述
value                   String             指定事务管理
propagation             enum: Propagation 事务传播行为
isolation               enum: Isolation 事务隔离级别
timeout                 Int(单位为秒)  事务超时时间
readOnly                boolean            是否只读
rollbackFor             Class[] 引起回滚的异常类数组
rollbackForClassName   String[] 引起回滚的异常类名称数组
noRollbackFor           Class[] 不会引起回滚的异常类数组
noRollbackForClassName  String[] 不会引起回滚的异常类名称数组

无论您在 Spring 中选择声明式还是编程式事务管理,定义正确的TransactionManager实现都是绝对必要的,TransactionManager的实现通常与它们工作的环境有关:JDBC、JTA、Hibernate 等等。


如果是纯 JDBC,则使用DataSourceTransactionManager,如下代码所示。


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>



如果是Hibernate,则使用HibernateTransactionManager。(Hibernate相较于纯JDBC多了一层sessionFactory)


<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>


2.本地事务源码之事务方法是如何被AOP代理拦截到的?


2.1.现象(单数据源多DB)


如下图所示,由于test_1和test_2这两个DB在同一个数据源下面,因此是本地事务,spring事务是直接就可以支持的。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


@Service
@Transactional
public class MultiDBServiceImpl implements IMultiDBService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void save() {
        String sql1 = "insert into test_2.t_class(Fname,Fnum) values("303班",30);";
        String sql2 = "insert into test_1.t_student(Fname,Fage,Fclass) values("曹操",30,3);";
        jdbcTemplate.execute(sql1);
        jdbcTemplate.execute(sql2);

        // 回滚
        throw new RuntimeException();
    }
}



CREATE TABLE t_student (
  Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  Fage int(255) NULL DEFAULT NULL,
  Fclass int(255) NULL DEFAULT NULL,
  PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

CREATE TABLE t_class (
  Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  Fnum int(11) NULL DEFAULT NULL,
  PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;



如果没打@Transactional注解,则直接进入目标方法,否则,进入到cglib生成的代理类中,如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


可以看到,cglib生成的代理为是com.bobo.springbootdemo.service.impl.MultiDBServiceImpl$$EnhancerBySpringCGLIB$$2afb0e3b类型,且实现了3个接口(其中Advised接口颇为重要),继承自目标类即com.bobo.springbootdemo.service.impl.MultiDBServiceImpl。


2.2.如何生成AOP代理类


AOP代理对象的生成是在Bean初始化方法中,有一些BeanPostProcessor对象,其中有一个叫AnnotationAwareAspectJAutoProxyCreator,是专门用于创建代理对象的,AnnotationAwareAspectJAutoProxyCreator是AbstractAutoProxyCreator的子类,AbstractAutoProxyCreator是BeanPostProcessor的子类,AbstractAutoProxyCreator实现了postProcessAfterInitialization方法,如下所示。


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


wrapIfNecessary方法核心代码如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


这里分两步叙述:

  • 如何获取该bean的Interceptors?

  • 如何创建代理对象?


2.2.1.如何获取该bean的Interceptors?


getAdvicesAndAdvisorsForBean方法点进去,会找到如下方法。


protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 找到候选的Advisors
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 从候选Advisors中找到符合条件的Advisors,判断是否符合条件前面已经介绍过了,说白了就是有事务注解
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);

    extendAdvisors(eligibleAdvisors);

    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}


下面介绍如何找候选的Advisors。


通过一路跟踪,最终是走到了BeanFactoryAdvisorRetrievalHelper类的findAdvisorBeans方法。


候选的Advisors的寻找方法实际上就等价于从Bean工厂中找到所有Advisor类型的bean。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


如上图所示,会从BeanFactory中找到一个beanName为org.springframework.transaction.config.internalTransactionAdvisor、类型为Advisor的bean BeanFactoryTransactionAttributeSourceAdvisor,该bean通过依赖注入了Advice类型的advice属性,TransactionInterceptor是Advice的子类。


通过全局搜索可以得知,TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor这两个bean的配置在ProxyTransactionManagementConfiguration类里面,如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


BeanFactoryTransactionAttributeSourceAdvisor间接继承自PointcutAdvisor,实现了getPointcut方法,如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


TransactionAttributeSource只是个接口,那么它的实现是什么,如何找到切入点的?


TransactionAttributeSource实现前面的图中已经给出了,是AnnotationTransactionAttributeSource,我们来看看这个类是怎么找到事务方法的。


这个类中有两个重要的方法,源码如下。


@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
    return determineTransactionAttribute(clazz);
}

@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Method method) {
    return determineTransactionAttribute(method);
}


可以看到参数可以是Class类型也可以是Method类型,这说明@Transactional注解既可以作用于类上也可以作用于方法上。


点进findTransactionAttribute方法,如下所示。


@Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
    for (TransactionAnnotationParser parser : this.annotationParsers) {
        TransactionAttribute attr = parser.parseTransactionAnnotation(element);
        if (attr != null) {
            return attr;
        }
    }
    return null;
}


TransactionAnnotationParser的实现有:Ejb3TransactionAnnotationParser、JtaTransactionAnnotationParser和SpringTransactionAnnotationParser,由于这是Spring事务,因此点进去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注解,如果有,则进入parseTransactionAnnotation方法,parseTransactionAnnotation是用来解析Transactional注解的各种属性的。


下面通过debug验证一下。


通过debug,可以看到将Transactional注解打在MultiDBServiceImpl上之后,确实走到了findTransactionAttribute(Class<?> clazz)方法,如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的




2.2.2.如何创建代理对象?


AbstractAutoProxyCreator.createProxy方法如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


proxyFactory.getProxy方法如下所示。


public Object getProxy(@Nullable ClassLoader classLoader) {
  return createAopProxy().getProxy(classLoader);
}
// createAopProxy方法
protected final synchronized AopProxy createAopProxy() {
    // AopProxyFactory接口目前只有一种实现:DefaultAopProxyFactory
  return getAopProxyFactory().createAopProxy(this);
}
// DefaultAopProxyFactory的createAopProxy方法
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        return new JdkDynamicAopProxy(config);
      }
            // 这里的config实际上就ProxyFactory对象,ProxyFactory是ProxyConfig的子类
      return new ObjenesisCglibAopProxy(config);
    }
    else {
      return new JdkDynamicAopProxy(config);
    }
  }


ObjenesisCglibAopProxy是CglibAopProxy的子类,CglibAopProxy.getProxy方法如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


getProxy方法主要代码如下所示。


Enhancer enhancer = createEnhancer();
if (classLoader != null) {
  enhancer.setClassLoader(classLoader);
  if (classLoader instanceof SmartClassLoader &&
      ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
    enhancer.setUseCache(false);
  }
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

// 获取callbacks,其中就包括CglibAopProxy的内部类DynamicAdvisedInterceptor
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
  types[x] = callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks call above
enhancer.setCallbackFilter(new ProxyCallbackFilter(
    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);

// 创建代理类的Class对象和实例
return createProxyClassAndInstance(enhancer, callbacks);


2.3.执行目标方法时被CglibAopProxy拦截


点进去之后,发现被CglibAopProxy拦截,如下图所示。

Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的


2.4.总结


当用@Autowired注入IMultiDBService Bean并且调用它的save时,会经过如下过程:

1.  Bean实例化

2.  Bean初始化

3.  执行BeanPostProcessor(AbstractAutoProxyCreator)的postProcessAfterInitialization方法

4.  获取该bean的Advisors(获取候选的Advisors(事先配置好的))

5.  获取满足条件的Advisors

6.  使用Enhancer结合callbacks(DynamicAdvisedInterceptor)生成动态代理对象

7.  执行代理对象的方法(IMultiDBService.save)

8.  被DynamicAdvisedInterceptor(Callback)拦截

9.  获取Advisors链

10.  执行Advisors链(TransactionInterceptor)


后面就是TransactionInterceptor的逻辑了,本教程暂不介绍。


原文始发于微信公众号(初心JAVA):Spring系列 第七弹 Spring事务源码分析之事务方法是如何被AOP代理拦截到的