漫谈Spring事务管理原理

6 篇文章 0 订阅
2 篇文章 0 订阅

看我的文章能减少bug哦, 请微信搜索公众号 tastejava 学习加思考,品味java之美. 一起把代码安排的明明白白~

前置知识

代理模式概念

代理模式大家应该都很熟悉了. 所谓的代理模式就是将原始方法包裹起来, 在原始方法执行前后插入一些逻辑, 比如日志记录. 比如下面我们提到的添加开始事务, 归滚/提交事务逻辑. 举个例子, 下方代码就对HashMap的put方法进行了代理, 代理逻辑是在真实put方法前后打印日志.

public class ProxyMap {
    // 被代理的真实类实例   
    private Map<String, String> map = new HashMap<>();
    // 代理方法
    public void put(String key, String value) {
        log.info("执行了put方法");
        // 真实方法
        map.put(key, value);
        log.info("put方法执行完毕");
    }
}

常见代理实现方式

了解了代理模式概念之后, 我们再看一下常见的代理实现方式. 除了上面原始的实现之外, 常见的实现方式有三种, 原本准备了各种方式的官方文档, 但是受限于篇幅, 还是通俗易懂的给大家概括一下吧, 对细节感兴趣的话可以去看官方文档:

  1. Jdk动态代理: Jdk动态代理基于接口实现, 仅支持public方法, 所以业务类必须实现接口, 要增强的方法必须是接口里的public方法.
  2. Cglib: Cglib基于继承实现, 因此业务类不需要实现接口, 由于子类可以访问父类public/protected方法, 所以除了public方法之外, 也支持代理protected方法
  3. AspectJ: 支持多种织入增强方式, 直接改变字节码, 所以没有权限限制, public/private/protected甚至构造器都可以代理增强

事务概念

事务的概念大家应该都很熟悉了, 即并发执行sql时有一些原子性, 一致性, 隔离性, 持久性(ACID)的事务特性, 就不展开来说了. 从使用的角度来看就是执行sql前begin开启事务, 结束后根据情况rollback回滚或者commit事务. 在Spring使用的角度来看, 实现事务有显示编程式和注解声明式两种方式.

Spring中的事务用法

编程式事务

编程式事务即代码显式的开启事务与提交/回滚事务, 在多个事务方法中重复逻辑较多, 因此目前主要使用的是声明式事务. 编程式事务使用方式不再赘述.

注解声明式事务

声明式事务在需要保证事务执行的public方法上标记@Transactional注解即可. 通常会设置一下所有类型Exception都回滚事务. 即

@Transactional(rollbackFor = Exception.class)

Spring声明式事务原理

声明式事务的实现总结起来就是一句话, Spring通过代理业务类, 对标注了@Transactional注解的业务类进行事务增强, 代理增强后的方法执行前后做了开启事务, 回滚/提交事务操作.

Spring实现代理时的抉择

Spring创建代理时用了工厂模式, 具体逻辑在org.springframework.aop.framework.DefaultAopProxyFactory#createAopPeoxy方法中. 感兴趣可以实际翻一翻代码, 核心代码如下.

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

我们可以看到在代理类实现时, 优先使用Jdk动态代理实现代理, 否则才降级为使用Cglib进行代理. 为什么这样设计呢, 俗话说"勿在浮沙筑高台", JDK自带功能当然比三方框架要更可靠, 这样也可以确保享受到JDK版本迭代带来的性能提升. 但是JDK动态代理时基于接口实现的, Cglib代理是基于继承实现的. 为了让没有实现接口的类也能使用Spring代理后的增强功能, 所以在类没有实现接口时使用Cglib完成代理.

上面我们了解了Spring实现代理时技术选型上的衡量与抉择, 采用JDK动态代理来实现代理增强也正是声明式事务仅public方法生效的原因 – JDK动态代理只能代理接口里的public方法.

这时又引出一个问题, 当类没有实现接口时, Spring使用Cglib实现代理, Cglib代理是基于继承的, 代理类可以访问到真实类的public, protected方法, 为什么这种情况也仅支持增强public方法呢? 这块翻了一下文档, Spring为了让降级使用Cglib实现代理的行为与JDK代理情况的行为一致, 代码逻辑限制了不管哪种情况, 都只对public方法进行事务增强支持, 如果需要对protected/private/构造器方法进行增强, 请使用AspectJ. 官方文档原文如下:

Due to the proxy-based nature of Spring’s AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn’t applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!
If your interception needs include protected/private methods or even constructors, consider the use of Spring-driven native AspectJ weaving instead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.

具体声明式事务功能代码实现原理

先看一下将普通方法代理增强为自动开启, 提交/回滚事务的逻辑:

// 增强添加事务开启, 回滚/提交功能的逻辑如下
// 方法全路径为org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
    final InvocationCallback invocation) throws Throwable {
// 获取事务属性源, 无需关注
  TransactionAttributeSource tas = getTransactionAttributeSource();
// 获取事务属性, 非public方法会返回null, 即添加了只有public方法
// 支持声明式事务的限制
  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)) {
// 开启事务
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    Object retVal;
    try {
// 调用真实方法的业务逻辑
      retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
// 出现异常时执行回滚操作
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
    finally {
// 清除事务信息
      cleanupTransactionInfo(txInfo);
    }
// 提交事务
    commitTransactionAfterReturning(txInfo);
    return retVal;
  }
}

了解了实现方式和从官方文档找到限制public方法原因之后, 让我们再看一下具体是在哪里做出了仅支持public方法的限制. 具体方法路径与核心代码解析如下:

// 具体代码如下方法, 详细代码略.
// org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
// 在invokeWithinTransaction方法中分为两步开启事务
//    1. 获得事务属性, 方法内调用org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#getTransactionAttribute
//    2. 开启事务, 调用org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary, 再在此方法内调用org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
// 限制public方法是在第2步中createTransactionIfNecessary做出的, 
// 如果是非public方法, 不会返回事务属性(getTransactionAttribute
// 中的computeTransactionAttribute方法), 后续开启事务方法也不会
// 开启事务, 具体代码如下:
// org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
  // Don't allow no-public methods as required.
  if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    return null;
  }
  // ...省略
}

The End

从代码实现我们可以看到, 并没有什么神奇的魔法, 都是现成的JDK能力. 厉害之处在于Spring开发者发现了"声明式事务"这个需求, 并且通过AOP的思路将需求进行了实现.
希望大家通过文章有所收获. 学习加思考, 品味java之美.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值