Spring 源码分析:不得不重视的 Transaction 事务(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 今天,正式介绍一下Java极客技术知识星球 SpringBoot 精髓之 SpringBoot-starter Spring 源码学习(八) AOP 使用和实现原理 Java:前程似锦的 NIO 2.0 我们谈谈面试技巧(初入职场年轻人该学的)

接着看我们的自动创建代理器是如何创建的:

AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element)

public static void registerAutoProxyCreatorIfNecessary(
        ParserContext parserContext, Element sourceElement) {
    BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
    if (beanDefinition != null) {
        // 注册的 beanName 是 org.springframework.aop.config.internalAutoProxyCreator
        parserContext.registerComponent(
                new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
    }
}

在这一步中,注册了一个 beanNameorg.springframework.aop.config.internalAutoProxyCreatorbeanInfrastructureAdsivorAutoProxyCreator,下图是它的继承体系图:

8.jpg

infrastructrue_advisor_auto_proxy_creator_diagram

可以看到,它实现了 InstantiationAwareBeanPostProcessor 这个接口,也就是说在 Spring 容器中,所有 bean 实例化时,Spring 都会保证调用其 postProcessAfterInitialization 方法。

与上一篇介绍的 AOP 代理器一样,在实例化 bean 的时候,调用了代理器父类 AbstractAutoProxyCreatorpostProcessAfterInitialization 方法:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        // 组装 key
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 如果适合被代理,则需要封装指定的 bean
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

其中关于 wrapIfNecessory 方法,在上一篇 AOP 中已经详细讲过,这里讲下这个方法做了什么工作:

  1. 找出指定 `bean` 对应的增强器
  2. 根据找出的增强器创建代理

与创建 AOP 代理相似的过程就不再重复说,讲下它们的不同点:


判断目标方法是否适合 canApply

AopUtils#canApply(Advisor, Class, boolean)

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) {
        PointcutAdvisor pca = (PointcutAdvisor) advisor;
        return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
        // It doesn't have a pointcut so we assume it applies.
        return true;
    }
}

我们在前面看到,TransactionAttributeSourceAdvisor 的父类是 PointcutAdvisor,所以在目标方法判断的时候,会取出切点信息 pca.getPointcut()

我们之前注入的切面类型 beanAnnotationTransactionAttributeSource,通过下面的方法包装,最后返回对象类型是 TransactionAttributeSourcePointcut 的切点信息

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
    @Override
    @Nullable
    protected TransactionAttributeSource getTransactionAttributeSource() {
        // 实现父类的方法,在子类中进行了扩展,返回之前在标签注册时的AnnotationTransactionAttributeSource
        return transactionAttributeSource;
    }
};

匹配标签 match

在匹配 match 操作中,区别的是 AOP 识别的是 @Before@After,而我们的事务 TX 识别的是 @Transactional 标签。

判断是否是事务方法的入口方法在这:

org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut#matches

@Override
public boolean matches(Method method, Class<?> targetClass) {
    // 事务切点匹配的方法
    TransactionAttributeSource tas = getTransactionAttributeSource();
    return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

那它到底到哪一步解析事务注解的呢,继续跟踪代码,答案是:

AnnotationTransactionAttributeSource#determineTransactionAttribute

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

在这一步中,遍历注册的注解解析器进行解析,由于我们关注的是事务解析,所以直接定位到事务注解的解析器:

SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
    // 解析事务注解的属性
    AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
            element, Transactional.class, false, false);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    }
    else {
        return null;
    }
}

首先判断是否含有 @Transactional 注解,如果有的话,才继续调用 parse 解析方法:

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
    RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
    // 注释 9.4 解析事务注解的每一个属性
    Propagation propagation = attributes.getEnum("propagation");
    rbta.setPropagationBehavior(propagation.value());
    Isolation isolation = attributes.getEnum("isolation");
    rbta.setIsolationLevel(isolation.value());
    rbta.setTimeout(attributes.getNumber("timeout").intValue());
    rbta.setReadOnly(attributes.getBoolean("readOnly"));
    rbta.setQualifier(attributes.getString("value"));
    List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
    for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
        rollbackRules.add(new RollbackRuleAttribute(rbRule));
    }
    for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
        rollbackRules.add(new RollbackRuleAttribute(rbRule));
    }
    for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
    }
    for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
    }
    rbta.setRollbackRules(rollbackRules);
    return rbta;
}

小结

通过上面的步骤,完成了对应类或者方法的事务属性解析。

主要步骤在于寻找增强器,以及判断这些增强器是否与方法或者类匹配。

如果某个 bean 属于可被事务增强时,也就是适用于增强器 BeanFactoryTransactionAttributeSourceAdvisor 进行增强。

之前我们注入了 TransactionInterceptorBeanFactoryTransactionAttributeSourceAdvisor 中,所以在调用事务增强器增强的代理类时,会执行 TransactionInterceptor 进行增强。同时,也就是在 TransactionInterceptor 类中的 invoke 方法中完成整个事务的逻辑。


运行


事务增强器 TransactionInterceptor

TransactionInterceptor 支撑着整个事务功能的架构。跟之前 AOPJDK 动态代理 分析的一样,TransactionInterceptor 拦截器继承于 MethodInterceptor,所以我们要从它的关键方法 invoke() 看起:

public Object invoke(MethodInvocation invocation) throws Throwable {
    // 注释 9.5 执行事务拦截器,完成整个事务的逻辑
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

实际调用了父类的方法:TransactionAspectSupport#invokeWithinTransaction

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
        final InvocationCallback invocation) throws Throwable {
    // 如果transaction属性为null,则该方法是非事务性的
    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)) {
        // 声明式事务处理
        // 标准事务划分 : 使用 getTransaction 和 commit / rollback 调用
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal;
        try {
            //传入的是回调函数对象:invocation.proceed。 执行被增强的方法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 异常回滚
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 清除信息
            cleanupTransactionInfo(txInfo);
        }
        // 提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    else {
        // 编程式事务处理
        final ThrowableHolder throwableHolder = new ThrowableHolder();
        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
            ...
            return result;
        }
    }
}

贴出的代码有删减,简略了错误异常的 try / catch 和编程式事务处理的逻辑。因为我们更多使用到的是声明式事务处理,就是在 XML 文件配置或者 @Transactional 注解编码,实际通过 AOP 实现,而编程式事务处理是通过 Transaction Template 实现,比较少使用到,所以省略了这部分处理代码。


事务管理器

通过该方法,确定要用于给定事务的特定事务管理器

TransactionAspectSupport#determineTransactionManager

protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
    // Do not attempt to lookup tx manager if no tx attributes are set
    // 注释 9.6 寻找事务管理器
    if (txAttr == null || this.beanFactory == null) {
        // 如果没有事务属性或者 BeanFactory 为空时,从缓存里面寻找
        return asPlatformTransactionManager(getTransactionManager());
    }
    String qualifier = txAttr.getQualifier();
    // 如果注解配置中指定了事务管理器,直接取出使用
    if (StringUtils.hasText(qualifier)) {
        return determineQualifiedTransactionManager(this.beanFactory, qualifier);
    }
    else if (StringUtils.hasText(this.transactionManagerBeanName)) {
        return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
    }
    else {
        // 上面步骤都没找到,最后才去容器中,根据 className 来寻找
        PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager());
        ...
        return defaultTransactionManager;
    }
}

由于最开始我们在 XML 文件中配置过 transactionManager 属性,所以该方法在我们例子中将会返回类型是 DataSourceTransactionManager 的事务管理器,下面是 DataSourceTransactionManager 的继承体系:

9.jpg

它实现了 InitializingBean 接口,不过只是在 afterPropertiesSet() 方法中,简单校验 dataSource 是否为空,不细说这个类。


事务开启

TransactionAspectSupport#createTransactionIfNecessary

protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
    // 如果没有名称指定则使用方法唯一标识,并使用 DelegatingTransactionAttribute 包装 txAttr
    if (txAttr != null && txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName() {
                return joinpointIdentification;
            }
        };
    }
    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            // 获取 TransactionStatus
            status = tm.getTransaction(txAttr);
        }
    }
    // 根据指定的属性与 status 准备一个 TransactionInfo
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

在创建事务方法中,主要完成以下三件事:

  1. 使用 `DelegatingTransactionAttribute` 包装 `txAttr` 实例
  2. 获取事务:`tm.getTransaction(txAttr)`
  3. 构建事务信息:`prepareTransactionInfo(tm, txAttr, joinpointIdentification, status)`

核心方法在第二点和第三点,分别摘取核心进行熟悉。


获取 TransactionStatus

status = tm.getTransaction(txAttr);

由于代码较长,直接来总结其中几个关键点

获取事务

创建对应的事务实例,我们使用的是 DataSourceTransactionManager 中的 doGetTransaction 方法,创建基于 JDBC 的事务实例。

protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    // 如果当前线程已经记录数据库链接则使用原有链接
    ConnectionHolder conHolder =
            (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    // false 表示非新创建连接
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

其中在同一个线程中,判断是否有重复的事务,是在 TransactionSynchronizationManager.getResource(obtainDataSource()) 中完成的,关键判断逻辑是下面这个:

private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");
private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    Object value = map.get(actualKey);
    // Transparently remove ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

结论:resources 是一个 ThreadLocal 线程私有对象,每个线程独立存储,所以判断是否存在事务,判断的依据是当前线程、当前数据源(DataSource)中是否存在活跃的事务 - map.get(actualKey)


处理已经存在的事务

根据前面说的,判断当前线程是否存在事务,判断依据为当前线程记录的连接不为空且连接中(connectionHolder)中的 transactionActive 属性不为空,如果当前线程存在事务,将根据不同的事务传播特性进行处理。具体代码逻辑如下:

if (isExistingTransaction(transaction)) {
    // Existing transaction found -> check propagation behavior to find out how to behave.
    // 当前线程存在事务,分情况进行处理
    return handleExistingTransaction(def, transaction, debugEnabled);
}

PROPAGATION_NEVER

在配置中配置设定为 PROPAGATION_NEVER,表示该方法需要在非事务的环境下运行,但处于事务处理的状态(可能是外部带事务的方法调用了非事务的方法),将会抛出异常:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
    }

PROPAGATION_NOT_SUPPORTED

如果有事务存在,将事务挂起,而不是抛出异常:

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    Object suspendedResources = suspend(transaction);
    boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    return prepareTransactionStatus(
            definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}


相关文章
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
75 1
|
27天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
15天前
|
SQL Java 关系型数据库
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
43 9
|
22天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
69 13
|
1月前
|
缓存 安全 Java
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
54 2
Spring高手之路26——全方位掌握事务监听器
|
1月前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
2月前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
66 1
Spring高手之路24——事务类型及传播行为实战指南
|
2月前
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
|
2月前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
86 3
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
44 1