Spring AOP + Transactional源码解析

作者: 进击的阿黑 | 来源:发表于2020-09-11 10:51 被阅读0次

Spring AOP应用于多数场景

  • 缓存
  • 权限
  • 懒加载
  • 日志
  • 事务
  • 。。。

这一篇将通过AOP源码的实现层面,结合事务的传播机制,来理解AOP是如何管理事务的。

生成AopProxy代理

Spring在启动期间,会将待注入的类注入到容器中,期间它会判断该类是否需要被代理,是的话将会创建该类实例的代理对象,代码片段如下
方法位于org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

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

先看一下生成代理对象的时序图


20200827145310512_991703414.png

检测是否要代理bean(AbstractAutoProxyCreator#wrapIfNecessary)

  • 检查是否有必要包装一下Bean,即是否需要往bean上添加代理对象,具体的检测逻辑如下
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 获取要在自动代理中使用的候选顾问(增强器,由Advisor和Advice组成)列表
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 过滤得到合格的候选顾问列表
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}
  • 检测返回的可用的增强器(AdvisorAdvice)列表后,如果列表不为null,则将其作为被代理的bean的状态缓存起来,并且开始创建代理对象
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
    // 缓存当前bean的代理状态
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    // 缓存代理类实例
    this.proxyTypes.put(cacheKey, proxy.getClass());
    return proxy;
}

创建代理工厂,通过工厂创建bean的代理对象

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    // 创建代理工厂,并且复制当前实例的相关属性
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    // 是否设置直接代理目标类,而不是目标类接口
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    // 根据可用的增强器列表构建真正适用于该bean的增强器列表
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    // 设置目标代理类
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
    return proxyFactory.getProxy(getProxyClassLoader());
}

代理工厂选取合适的AOP代理(ProxyFactory#getProxy)

上面创建的工厂代理类,配置了相应的属性后,将选取合适的AOP代理,并且生成代理对象

创建AOP代理

protected final synchronized AopProxy createAopProxy() {
    // 如果代理未激活,则激活该代理配置
    if (!this.active) {
        activate();
    }
    // 获取aop代理工厂类,并创建aop代理
    return getAopProxyFactory().createAopProxy(this);
}

Spring内置的aop代理有两种:JDK和Cglib。之前创建的代理工厂在下面创建方法作为形参

  • 使用JDK动态代理
    • proxyTargetClass(是否直接代理目标类)为false
    • 指定的目标类为接口类型
    • 当且仅当使用newProxyInstancegetProxyClass动态将目标类生成代理类时
  • 使用Cglib代理
    • proxyTargetClass(是否直接代理目标类)为true
    • 指定的目标类不为接口类型
    • 未使用newProxyInstancegetProxyClass动态将目标类生成代理类时
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}
  • 在Spring5中,AOP默认还是用JDK动态代理,如果被代理类未实现接口才使用Cglib代理
  • 而在Springboot2.x中,AOP已默认使用Cglib代理
    即被代理类有没有实现接口,它都使用Cglib代理

那如何更改为JDK代理呢?在Springboot中都是依靠自动装配来实现的,在启动过程中会加载各种配置,其中就涉及了AOP相关的配置,通过spring.factories可知相关配置代码在org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
从代码得知,Springboot2.x默认使用Cglib代理在于配置了spring.aop.proxy-target-class=true,故要切为JDK代理可配置spring.aop.proxy-target-class=false

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
                matchIfMissing = false)
        static class JdkDynamicAutoProxyConfiguration {

        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
    static class ClassProxyingConfiguration {

        ClassProxyingConfiguration(BeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        }

    }

}

使用类加载器创建代理对象

类加载器一般使用应用类加载器AppClassLoader,也可以自己实现了自定义加载器。

Cglib代理

20200827145333136_1411722303.png

Cglib代理是委托给CglibAopProxy去创建的,创建过程如下

  • 获得目标类rootClass
  • 如果目标类已经被代理,则获取它的父类作为目标类
  • 验证目标类,检查是否需要写日志
  • 创建Enhancer对象(即代理增强器,类比于jdk自带的Proxy),准备创建代理类
    • 设置目标类为代理增强器父类
    • 设置要实现的接口,有则使用目标类的接口,默认还会实现SpringProxyAdvised接口
    • 覆盖命名策略,一般生成的代理类都是有对应的命名策略,在spring中,命名规范是:目标类名+$$EnhancerBySpringCGLIB
    • 设置生成字节码的策略,默认使用DefaultGeneratorStrategy,Spring中用了GeneratorStrategy的另一种实现ClassLoaderAwareGeneratorStrategy
    • 设置回调过滤器,根据被拦截的方法执行不同的处理逻辑。CallbackFilter#accpet在实际调用中,会根据被拦截的方法返回对应的索引,在Callback中会根据索引值拿到对应的回调处理逻辑
  • 创建目标类的代理类和目标类的代理类实例
    • 创建代理类proxyClass
    • 创建代理类实例proxyInstance
    • 设置回调组Callbacks,和CallbackFilter结合使用,回调组中有一个通用回调处理器DynamicAdvisedInterceptor,其intercept()是拦截的首入口。
public Object getProxy(@Nullable ClassLoader classLoader) {
    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            proxySuperClass = rootClass.getSuperclass();
            // ...
        }

        validateClassIfNecessary(proxySuperClass, classLoader);

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

        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);

        return createProxyClassAndInstance(enhancer, callbacks);
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                ": Common causes of this problem include using a final class or a non-visible class",
                ex);
    }
    catch (Throwable ex) {
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}

JDK代理

20200827171028608_1306162524.png

JDK代理是委托给JdkDynamicAopProxy去创建的,创建过程如下

  • 设置要实现的接口,有则使用目标类的接口,默认还会实现SpringProxyAdvised接口
  • 查找目标类是否定义了equalshashcode,是的话分别标记equalsDefined为true,hashCodeDefined为true
  • 调用Proxy.newProxyInstance生成目标类的代理类实例
public Object getProxy(@Nullable ClassLoader classLoader) {
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

事务传播机制

在Spring中,事务机制与AOP做了结合,通过AOP封装了事务管理的代码,接下来将会通过AOP理解事务的传播机制。

Propagation七种传播机制

相关枚举在org.springframework.transaction.annotation.Propagation

机制 说明 场景
PROPAGATION_REQUIRED 若线程上下文中存在事务则加入,不存在则创建一个新事务 最常使用,因为它是Spring缺省的传播机制
PROPAGATION_SUPPORTS 若线程上下文中存在事务则加入,不存在则以非事务的模式执行 -(使用场景较少)
PROPAGATION_MANDATORY 若线程上下文中存在事务则加入,不存在则抛出异常 常用于检测调用代码的上下文是否存在事务,提醒必须以事务运行
PROPAGATION_REQUIRES_NEW 若线程上下文中存在事务则暂时挂起****,并创建一个新事务,执行完后才恢复其他上下文事务 常用于内层事务异常会导致回滚时,外层事务(一般)不会被回滚
PROPAGATION_NOT_SUPPORTED 若线程上下文中存在事务则挂起,并以非事务的模式执行,执行完才恢复上下文事务 常用于减少事务范围,同时避免内层事务异常而导致不必要的全局回滚的场景
PROPAGATION_NEVER 若线程上下文中存在事务,则抛出异常 -(使用场景较少)
PROPAGATION_NESTED 若线程上下文中存在则嵌套事务执行,不存在则创建一个新事务。
它仅支持“在特定的事务管理器DataSourceTransactionManager上,以及使用jdbc3.0驱动程序”的使用,其提供了一个“save point”的父子事务的概念,在进入子事务之前建立一个“save point”,当子事务回滚时会先滚到“save point”,而父事务可以选择性回滚。
-(使用场景较少)

实践

关于传播机制,在上面已经做了描述,然而事实真的如以上描述一般的简单吗,接下来模拟做些测试用例,看看结论。

首先,先清楚两个点

  • Spring默认传播机制是PROPAGATION_REQUIRED
  • @Transactional中默认回滚异常是RuntimeException,但是p3c建议我们显示的rollback

场景1:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出Runtime异常

@Transactional(rollbackFor = Exception.class)
public void addUser() {
    userMapper.insert(User.create("Jerry", 21));
    userChannelService.addUserChannel();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannel() {
    throw new NullPointerException("在不同service内假装抛出Runtime异常");
}

场景2:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出Runtime异常但是进行了异常捕获

@Transactional(rollbackFor = Exception.class)
public void addUserV2() {
    userMapper.insert(User.create("Jerry", 22));
    userChannelService.addUserChannelV2();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannelV2() {
    try {
        throw new NullPointerException("在不同service内假装抛出Runtime异常并捕获");
    } catch (NullPointerException e) {
    }
}

以上是在内层事务捕获,同样考虑一下在外层事务捕获的结果

场景3:两个service A/B, A方法调用B方法且都开启了事务,B方法抛出Runtime异常但是进行了异常捕获,且B方法事务指定了隔离级别为Propagation.REQUIRES_NEW

@Transactional(rollbackFor = Exception.class)
public void addUserV3() {
    userMapper.insert(User.create("Jerry", 23));
    userChannelService.addUserChannelV3();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV3() {
    try {
        userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
        throw new NullPointerException("在不同service内假装抛出Runtime异常并捕获,但是我设置了隔离级别为新建事务");
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
}

以上是在内层事务捕获,同样考虑一下在外层事务捕获的结果

场景4:两个service A/B, A方法调用B方法且都开启了事务,B方法抛出Runtime异常,且B方法事务指定了隔离级别为Propagation.REQUIRES_NEW

@Transactional(rollbackFor = Exception.class)
public void addUserV4() {
    userMapper.insert(User.create("Jerry", 24));
    userChannelService.addUserChannelV4();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV4() {
    throw new NullPointerException("在不同service内假装抛出Runtime异常,但是我设置了隔离级别为新建事务");
}

场景5:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出自定义异常(非Runtime)

@Transactional(rollbackFor = RuntimeException.class)
public void addUserV5() throws CustomException {
    userMapper.insert(User.create("Jerry", 25));
    userChannelService.addUserChannelV5();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV5() throws CustomException {
    throw new CustomException("在不同service内假装抛出自定义异常");
}

以上指定回滚异常为RuntimeException,要是设置为Exception,会是什么结果呢?

场景6:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出自定义异常(非Runtime)但是进行了异常捕获

@Transactional(rollbackFor = RuntimeException.class)
public void addUserV6() {
    userMapper.insert(User.create("Jerry", 26));
    userChannelService.addUserChannelV6();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV6() {
    try {
        userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
        throw new CustomException("在不同service内假装抛出自定义异常并捕获");
    } catch (CustomException e) {
        e.printStackTrace();
    }
}

以上指定回滚异常为RuntimeException,要是设置为Exception,会是什么结果呢?

场景7:service A, a方法调用内部方法b且都开启了事务(默认级别Propagation.REQUIRED),b方法抛出Runtime异常

@Transactional(rollbackFor = Exception.class)
public void addUserV7() {
    userMapper.insert(User.create("Jerry", 27));
    exception();
}

@Transactional(rollbackFor = Exception.class)
public void exception() {
    throw new NullPointerException("在同个service内假装抛出异常");
}

场景8:service A, a方法调用内部方法b且都开启了事务(默认级别Propagation.REQUIRED),b方法抛出Runtime异常,且c方法事务指定了隔离级别为Propagation.REQUIRES_NEW

@Transactional(rollbackFor = Exception.class)
public void addUserV8() {
    userMapper.insert(User.create("Jerry", 28));
    exceptionV2();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void exceptionV2() {
    throw new NullPointerException("在同个service内假装抛出异常,但是我设置了隔离级别为新建事务,也会回滚");
}

AopProxy代理事务的操作过程

上面提供了8个场景,在获取测试结果之前,先看下AOP代理事务的操作流程

Cglib代理过程

同样,Cglib代理过程也是委托给CglibAopProxy去执行的,那结合上面创建代理对象的流程,我们如何得知代理执行的入口和代理流程呢?

在Cglib代理对象的创建过程中设置的回调组Callbacks中,回调类如下

Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
Callback[] mainCallbacks = new Callback[] {
        aopInterceptor,  // for normal advice
        targetInterceptor,  // invoke target without considering advice, if optimized
        new SerializableNoOp(),  // no override for methods mapped to this
        targetDispatcher, this.advisedDispatcher,
        new EqualsInterceptor(this.advised),
        new HashCodeInterceptor(this.advised)
};

结合前面提到的,它一般和CallbackFilter结合使用,CglibAopProxy#accpet在实际调用中,会根据被拦截的方法返回对应的索引,在Callback中会根据索引值拿到对应的回调处理逻辑,其实CglibAopProxy中已经声明了对应静态不可变的成员变量

private static final int AOP_PROXY = 0;
private static final int INVOKE_TARGET = 1;
private static final int NO_OVERRIDE = 2;
private static final int DISPATCH_TARGET = 3;
private static final int DISPATCH_ADVISED = 4;
private static final int INVOKE_EQUALS = 5;
private static final int INVOKE_HASHCODE = 6;

org.springframework.cglib.proxy.Enhancer#emitMethods中,会遍历被代理类的方法,并且设置切入点,初始默认会返回AOP_PROXY,即回调处理逻辑将在通用AOP回调DynamicAdvisedInterceptor#intercept中进行,具体看源码即可

好了,知道了代理的入口,再整理下代理流程

  • 调用方法method,首先拦截于DynamicAdvisedInterceptor#intercept
  • 获取目标类实例targetClass,根据method + targetClass从代理配置管理器AdvisedSupport中获取拦截器(Advice通知)链条chain
  • 创建一个Cglib方法调用对象CglibMethodInvocation,执行调用方法proceed,准备调用拦截器链条
  • CglibMethodInvocation中维护着当前拦截器索引currentInterceptorIndex,当它小于拦截器链条长度时,会自增并依次作为索引条件获取指定拦截器,并执行它的切入点TransactionInterceptor#invoke(),从名字可以看出是一个事务操作的拦截器,当自增到等于拦截器链条长度时,开始真正调用被代理方法;
  • 接下来会执行TransactionAspectSupport#invokeWithinTransaction,在被代理方法前先开启事务,接着回调到上一步,循环执行拦截器;
20200828124511456_281569522.png

事务执行过程

在Spring事务执行中,它提供了抽象事务管理器类AbstractPlatformTransactionManager来处理通用的事务处理流程,而它的子类将做具体的实现,如事务开始,事务提交、事务回滚等,如DataSourceTransactionManagerJpaTransactionManager等。

上面代理执行过程中,在事务拦截器阶段,将会执行一段事务操作,其中最核心的过程和代码片段如下

  • 开启事务
  • 执行被代理的业务方法
  • 异常则回滚事务
  • 提交事务
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

    Object retVal;
    try {
        retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    }
    finally {
        cleanupTransactionInfo(txInfo);
    }

    //...

    commitTransactionAfterReturning(txInfo);
    return retVal;
}

执行过程很清晰,值得注意的是,在completeTransactionAfterThrowing中,并不一定直接回滚,如果发现当前异常非指定的rollback异常,则会直接commit(),再将异常抛到外部,你觉得此 commit()真的会成功吗?看下面场景5的结果吧!

再看下回滚事务的代码,位于抽象事务管理器AbstractPlatformTransactionManager#processRollback,有下面几种回滚原因

  • 原因一:如果事务设置了“save point”,即使用了PROPAGATION_NESTED嵌套事务的传播机制,则回滚到“save point”
if (status.hasSavepoint()) {
    if (status.isDebug()) {
        logger.debug("Rolling back transaction to savepoint");
    }
    status.rollbackToHeldSavepoint();
}
  • 原因二:如果事务是新开事务(单一事务也算),则直接让该事务回滚
else if (status.isNewTransaction()) {
    if (status.isDebug()) {
        logger.debug("Initiating transaction rollback");
    }
    doRollback(status);
}
  • 原因三:如果当前事务有回滚标志,或者当前事务开启了全局事务回滚
if (status.hasTransaction()) {
    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
        if (status.isDebug()) {
            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
        }
        doSetRollbackOnly(status);
    }
    else {
        if (status.isDebug()) {
            logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
        }
    }
}
  • 原因四:以上几种都属于上述步骤的第三步:异常则回滚事务,即在commit()之前就导致的异常回滚,而当在请求commit()时检测到全局事务被标记了rollback-only,这是一种不期望的行为,也会导致回滚

commit()过程的回滚代码,位于AbstractPlatformTransactionManager#commit

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
    if (defStatus.isDebug()) {
        logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
    }
    processRollback(defStatus, true);
    return;
}

AbstractPlatformTransactionManager#processRollback中,会直接打印出错误信息:不期望的回滚异常

if (unexpectedRollback) {
    throw new UnexpectedRollbackException(
            "Transaction rolled back because it has been marked as rollback-only");
}

由于这里使用的具体实现是DataSourceTransactionManager,故实际的回滚操作doRollbackdoSetRollbackOnly将会在其中操作。

结合代理操作过程得出实践场景结果

场景1(默认级别Propagation.REQUIRED)

  • 结果:A/B都会回滚(从上一步分析得知,这里属于原因三的回滚行为)

  • 总结:A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,则B方法会回滚,而因为和A方法处于同一事务,也导致A方法会回滚。

场景2(默认级别Propagation.REQUIRED)

  • 结果:

    • 在A方法捕获:A、B会回滚(从上一步分析得知,这里属于原因四的回滚行为)
    • 在B方法捕获:A、B不会回滚
  • 总结:

    • A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在外层A方法捕获,则B方法会回滚,而因为和A方法的同一事务,也导致A方法会回滚(不管A方法是否有做捕获操作),此时会抛出"Transaction rolled back because it has been marked as rollback-only"
    • A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在内层捕获,则B方法不会回滚,A方法也不会回滚

场景3(A:默认级别Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)

  • 结果:

    • A方法一定不会回滚
    • 在A方法捕获:B会回滚(从上一步分析得知,这里属于原因二的回滚行为)
    • 在B方法捕获:B不会回滚
  • 总结:

    • A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在外层A方法捕获,则B方法会回滚,但是因为和A方法不是同一事务,所以A方法不会回滚
    • A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在内层B方法捕获,则B方法不会回滚,A方法也不会回滚

场景4(A:默认级别Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)

  • 结果:A/B方法都会回滚(从上一步分析得知,这里B方法的回滚属于原因二的回滚行为)

可能有些人会有疑问,我已经将B方法设置为开启事务,怎么还会导致A方法回滚呢?
很简单,从A方法看起,它指定回滚的异常是Exception,而B方法抛出了NPE异常后又没有捕获,所以尽管B方法开启了新的事务,但是是由A发起的且抛出NPE异常,所以A方法也回滚了。
故可以换另一种角度,当A方法指定回滚异常是RuntimeException,而B方法回滚异常为非Runtime异常,此时可以发现A方法并不会回滚!并且B方法指定回滚异常也为RuntimeException,则B方法也不会回滚!

  • 总结:A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,则A、B方法都会回滚

场景5(默认级别Propagation.REQUIRED)

  • 结果:两个方法同时指定回滚异常为RuntimeException,才不会回滚,否则都会回滚

在上面事务执行过程中,讲到了当前异常非rollback异常时会直接commit(),而此次commit()会不会成功呢?答案是不一定!
如果A/B都在同个事务中,并且内层B方法指定异常为RuntimeException,而抛出异常为自定义异常,则会commit(),由于没有捕获异常,故无论外层A方法指定什么异常,A/B方法的业务操作都会一起被回滚!(从上一步分析得知,这里的回滚属于原因二的回滚行为);那怎样才能让此次commit()成功呢,只要在内层B方法中进行捕获即可

  • 总结:
    • A类方法(回滚异常为Exception)调用B类方法(回滚异常为RuntimeException),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
    • A类方法(回滚异常为Exception)调用B类方法(回滚异常为Exception),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
    • A类方法(回滚异常为RuntimeException)调用B类方法(回滚异常为Exception),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
    • A类方法(回滚异常为RuntimeException)调用B类方法(回滚异常为RuntimeException),且都开启事务,若B类方法抛出自定义异常(非Runtime),则B会回滚,A不会回滚

场景6(默认级别Propagation.REQUIRED)

  • 结果:不会回滚
  • 总结:A类方法调用B类方法,且都开启事务,若B类方法抛出自定义异常并且捕获,则不会回滚

场景7(默认级别Propagation.REQUIRED)

  • 结果:会回滚(从上一步分析得知,这里的回滚属于原因二的回滚行为)
  • 总结:在同个类中a方法调用b方法,b方法不会开启新事务,即不会用到事务的传播机制

场景8(默认级别Propagation.REQUIRED)

  • 结果:会回滚(从上一步分析得知,这里的回滚属于原因二的回滚行为)
  • 总结:在同个类中a方法调用b方法,b方法无论设置什么都不会开启新事务,即不会用到事务的传播机制

相关文章

网友评论

    本文标题:Spring AOP + Transactional源码解析

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