初步走入 Spring 事务底层核心前置源码(上)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 初步走入 Spring 事务底层核心前置源码

前言

事务从 AOP 开始,有了 AOP 基础就可以学习 Spring 事务阶段了

事务加载、对象创建

一切从配置文件开始,提前把 BeanDefinition 定义信息准备好,到达实例化阶段再统一处理

代码

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.driverClassName=com.mysql.jdbc.Driver
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
  <property name="url" value="${jdbc.url}"/>
  <property name="driverClassName" value="${jdbc.driverClassName}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
  <constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="bookService" class="com.vnjohn.tx.xml.service.BookService">
  <property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.vnjohn.tx.xml.dao.BookDao">
  <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<aop:config>
  <aop:pointcut id="txPoint" expression="execution(* com.vnjohn.tx.xml.*.*.*(..))"/>
  <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="checkout" propagation="REQUIRED"/>
    <tx:method name="updateStock" propagation="REQUIRES_NEW"/>
  </tx:attributes>
</tx:advice>

${} 读取本地资源文件的变量,核心类是 PropertySourcesPlaceholderConfigurer,只要 XML 配置了 <context:property-placeholder> 标签元素,先会创建该类的 beanDefinition 信息,后会再去执行 BFPP 时先将这个类进行创建,然后执行 postProcessBeanFactory 方法进行资源文件的加载,加载完所有的资源文件以后,再对所有时用到占位符读取变量的地方进行赋值操作.

准备 BeanDefinition 信息

AopNamespaceHandler 执行器对应的解析类是 ConfigBeanDefinitionParser,TxNamespaceHandler 执行器对应的解析类是 TxAdviceBeanDefinitionParser

解析 <aop:config> 标签时,先会注册自动代理创建器,后续会用它来操作生成类的代理对象,接着会解析其下的子标签

// 解析aop:config子节点下的aop:pointcut/aop:advice/aop:aspect
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
  String localName = parserContext.getDelegate().getLocalName(elt);
  if (POINTCUT.equals(localName)) {
    parsePointcut(elt, parserContext);
  }
  else if (ADVISOR.equals(localName)) {
    parseAdvisor(elt, parserContext);
  }
  else if (ASPECT.equals(localName)) {
    parseAspect(elt, parserContext);
  }
}

在上述的案例配置文件中,有两个标签,<aop:pointcut> 会创建表达式切入点的定义信息:AspectJExpressionPointcut,会在其信息下追加 expression 字符串用于后面的类匹配和方法匹配、<aop:advisor> 创建 advice 持有者定义信息:DefaultBeanFactoryPointcutAdvisor,会其下信息设置 adviceBeanName 属性、pointcut-ref 属性,这两者都是字符串会在后续加载具体的 Bean

解析 <tx:advice> 标签时,会先执行父类 AbstractSingleBeanDefinitionParser#parseInternal 方法去构建一个最外层类型为 TransactionInterceptor 类型的定义信息,调用子类 TxAdviceBeanDefinitionParser#doParse 方法解析其属性和子节点信息,首先设置 transactionManager 属性值,如果有配置其属性就取设置的,没有取默认的 transaction-manager;随后判断其下的 <tx:attribute> 子节点「类型为 NameMatchTransactionAttributeSource」,超过一个会记录错误日志信息,再获取 <tx:attributes> 标签下所有的 <tx:method> 节点,每一个节点都作为 「RuleBasedTransactionAttribute 类型」& 设置该节点所配置的属性,最后所有的 <tx:method> 定义信息作为 value,key 为 nameMap 进行存储;nameMap 又作为 NameMatchTransactionAttributeSource 类型的一个属性.

class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
  // ...... 
  @Override
  protected Class<?> getBeanClass(Element element) {
    return TransactionInterceptor.class;
  }
}

tx:method 标签可以配置以下几个属性👇

  • 传播机制:propagation 默认值(REQUIRED)
  • 隔离级别:isolation 默认值(DEFAULT)
  • 超时时间:timeout 默认值(-1 不超时)
  • 是否只读:readOnly 默认值(false)

从以上流程图和文字描述来看,大部分都是需要待解析的属性,在实例化 bean 时会对这些类的属性进行具体的解析,解析整个 <tx:advice> 标签后,类的结构如下图所示👇

创建实例处理过程

依然按照以上的 xml 配置文件内容来进行流程讲解;加载完所有 Bean 定义信息,进行实例化过程

  • 普通 Bean【非内部的类型,比如 Advice、Pointcut、Advisor、AopInfrastructureBean】在执行时会触发 AbstractAutoProxyCreator 类调用 postProcessBeforeInstantiation(实例化之前的方法)),此时会将所有的 advisor 全部加载好存入缓存
  • 实例化完 bookService 以后,填充属性阶段,属性类型为 RunTimeBeanReference,接着进行 bookDao 实例化流程,实例化完 bookDao 以后,填充属性阶段,设置好相关的数据
  • 接着调用 AbstractAutoProxyCreator#postProcessAfterInitialization(初始化之后的方法)
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        // 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,
        // 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为key
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 判断当前bean是否正在被代理,如果正在被代理则不进行封装
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 如果它需要被代理,则需要封装指定的bean
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

前面所有的工作处理完以后,剩下的就是创建代理对象了,这里面涉及到会去创建具体的事务 advisor 增强器

  • 先将所有符合条件的 advisor 查找出来,再对其进行匹配,验证类、方法是否能够适配
  • 提供 hook 钩子方法用于对目标 advisor 进行扩展,会通过 advisor 持有者去加载其下的 advice 增强器,如果在一级缓存中获取不到该对象,那么就会走一个整体的创建 bean 过程,adviceBeanName:myPoint 这个 advice 对应的类型是 TransactionInterceptor ,之前在加载 beanDefinition 信息时它下面是存在两个属性的,实例化完、填充属性阶段将其补充后这个事务相关的 advice 就创建好了
  • 当所有的 advice 加载好以后,再添加一个新的 advice:ExposeInvocationInterceptor,将它置于首位,标为 0
  • 对 advice 进行拓扑排序后,接下来就是创建真正代理对象的过程了,这里不做过多阐述,如果是 CGLIB 通过 enhancer.create 进行创建,JDK 就通过 Proxy.newProxyInstance 进行创建.

目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
8天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
35 13
|
1月前
|
缓存 安全 Java
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
46 2
Spring高手之路26——全方位掌握事务监听器
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
54 2
|
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事务管理能力。
61 1
Spring高手之路24——事务类型及传播行为实战指南
|
2月前
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
71 9
|
2月前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
71 3