天天看点

Spring事务源码分析之事务方法是如何被AOP代理拦截到的?

文章目录

  • ​​1、spring声明式事务概述​​
  • ​​2、本地事务源码之事务方法是如何被AOP代理拦截到的?​​
  • ​​1、现象(单数据源多DB)​​
  • ​​2、如何生成AOP代理类​​
  • ​​1、如何获取该bean的Interceptors?​​
  • ​​2、如何创建代理对象?​​
  • ​​3、执行目标方法时被CglibAopProxy拦截​​
  • ​​4、总结​​

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代理拦截到的?

1、现象(单数据源多DB)

如下图所示,由于test_1和test_2这两个DB在同一个数据源下面,因此是本地事务,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事务源码分析之事务方法是如何被AOP代理拦截到的?

可以看到,cglib生成的代理为是​

​com.bobo.springbootdemo.service.impl.MultiDBServiceImpl$$ EnhancerBySpringCGLIB$$2afb0e3b​

​类型,

且实现了3个接口(其中Advised接口颇为重要),继承自目标类即com.bobo.springbootdemo.service.impl.MultiDBServiceImpl。

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事务源码分析之事务方法是如何被AOP代理拦截到的?

这里分两步叙述:

  • 如何获取该bean的Interceptors?
  • 如何创建代理对象?

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事务源码分析之事务方法是如何被AOP代理拦截到的?

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

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

Spring事务源码分析之事务方法是如何被AOP代理拦截到的?

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

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事务源码分析之事务方法是如何被AOP代理拦截到的?

2、如何创建代理对象?

AbstractAutoProxyCreator.createProxy方法如下图所示。

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事务源码分析之事务方法是如何被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);      

3、执行目标方法时被CglibAopProxy拦截

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

Spring事务源码分析之事务方法是如何被AOP代理拦截到的?

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)

继续阅读