8-基于Spring的框架-事务——8-2 事务工作详情(一) 扫描及相关注册

概要

过度

我们前面介绍了事务的一些特点、使用原因,并介绍了它的基本API的使用方法。

我们本节从上文的 Spring 使用事务的 demo 入手,大概介绍一下Spring-tx框架的实现流程,以加深对Spring的熟悉。

内容简介

开始具体介绍Spring-tx框架的实现原理,我们本文主要介绍事务相关的注册逻辑,后面会继续介绍在正常工作时的事务工作逻辑。

所属环节

事务具体介绍——扫描及相关注册

上下环节

上文: 事务引入

下文: 事务具体工作逻辑

源码解析

入口

我们上文介绍了demo,其实核心就是两个xml配置,一个是对自定义标签<tx:annotation-driven />的解析,一个是对特殊类型bean的注册。具体代码如下:

<tx:annotation-driven />

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

我们从自定义标签解析开始,一般这里就是对应框架的实现入口。之前讲过好多这种自定义标签的解读方法了,无非是

  1. 通过 META-INF/spring.handlers 确定定制包的命名空间解析处理器【此处为TxNamespaceHandler
  2. 通过标签解析处理器找到处理对应标签的类【此处为AnnotationDrivenBeanDefinitionParser

接下来就看解析逻辑就行了。

AnnotationDrivenBeanDefinitionParser解析逻辑

整体逻辑

我们先看他的整体解析逻辑吧:

public BeanDefinition parse(Element element, ParserContext parserContext) {
  registerTransactionalEventListenerFactory(parserContext);
  String mode = element.getAttribute("mode");
  if ("aspectj".equals(mode)) {
    // mode="aspectj"
    registerTransactionAspect(element, parserContext);
  } else {
    // mode="proxy"
    AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
  }
  return null;
}

先是registerTransactionalEventListenerFactory注册事务相关事件的监听组件,然后将事务相关注册进行委托。

根据上一篇文章的推测,事务的处理是通过切面实现的,所以注定要用到切面相关的东西,这里也提供了不同的切面集成方式:aspectj 和 AOP 。AOP 我们之前介绍过,优先选这种,毕竟这样读起代码比较轻松点。而且我们在日常使用事务时也没有配置过 aspectj ,因此猜测我们平时都是用的 AOP 实现策略。

我们大概看一下事件监听组件的代码即可:

private void registerTransactionalEventListenerFactory(ParserContext parserContext) {
  RootBeanDefinition def = new RootBeanDefinition();
  def.setBeanClass(TransactionalEventListenerFactory.class);
  parserContext.registerBeanComponent(new BeanComponentDefinition(def,
                                                                  TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME));
}

后面把主要精力放到事务相关的代码上。

基于AOP的事务注册

/**
    * Inner class to just introduce an AOP framework dependency when actually in proxy mode.
    */
private static class AopAutoProxyConfigurer {

  public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
    // 此处注册支持AOP的基础类
    AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

    // 如果没有手动注册或者扫描 TRANSACTION_ADVISOR_BEAN_NAME ,就在这里进行注册
    // TODO 这里是进行对应元素解析时进行调用,后面可能会有 TRANSACTION_ADVISOR_BEAN_NAME 的声明
    // 这里猜测应该对覆盖式声明有兼容,根据之前看的框架的东西,同id同类,应该没问题
    // TODO 正常我们就不会对这个BD进行注册,基本就靠这里了
    String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
    if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
      // 对封装了事务逻辑的切面进行声明、注册
      Object eleSource = parserContext.extractSource(element);

      // Create the TransactionAttributeSource definition.
      RootBeanDefinition sourceDef = new RootBeanDefinition(
        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
      sourceDef.setSource(eleSource);
      sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

      // Create the TransactionInterceptor definition.
      RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
      interceptorDef.setSource(eleSource);
      interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      registerTransactionManager(element, interceptorDef);
      interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
      String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

      // Create the TransactionAttributeSourceAdvisor definition.
      RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
      advisorDef.setSource(eleSource);
      advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
      advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
      if (element.hasAttribute("order")) {
        advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
      }
      parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);

      CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
      compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
      compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
      compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
      parserContext.registerComponent(compositeDef);
    }
  }
}

整体思路如下:

  1. 注册支持AOP的基础类以此启动AOP的相关功能

  2. 注册transactionAttributeSource的bean,用来存储代码类、方法和打的@Transactional注解的属性的映射关系

  3. 注册TransactionInterceptor,此方法中包含了对事物的实现逻辑,它实现了MethodInterceptor接口,由下面专门注册的Advisor进行封装和调用

    此处专门抽离出来感觉有两个原因:

    1. 避免将事物的实现逻辑和AOP紧耦合,增加通用性【就比如还有用aspectj类型的】
    2. 方便子类依赖事物控制逻辑进行进一步定制
  4. 注册BeanFactoryTransactionAttributeSourceAdvisor,它是一个实现了Advisor接口的类,用来通过AOP实现对事物的相关控制。

    角色:

    它引用了transactionAttributeSourceTransactionInterceptor,可以看作是两个功能的汇总

    功能:

    它实现了

    1. 判断是否进行增强
    2. 解析、记录类、方法坐标和注解配置的对应关系
    3. 根据解析处的配置进行事物逻辑的调用

    感觉更像是把前面的东西都串起来了。

接下来我们从BeanFactoryTransactionAttributeSourceAdvisor入手。

我们先回顾一下AOP中的大概逻辑:

  1. 先找出BeanFactory中所有实现了Advisor接口的类,然后实例化
  2. 判断增强器是否适用当前要处理的类
  3. 如果适用,就进行增强,然后返回
  4. 如果不适用,就结束

我们按照这个逻辑来看,就比较清楚了。

判断是否需要事物增强

BeanFactoryTransactionAttributeSourceAdvisor中,我们根据前面对AOP的介绍,发现在判断Advisor类型是Pointcut之后,我们会拿到Pointcut,然后调用里面的match方法进行判断。

BeanFactoryTransactionAttributeSourceAdvisor中,很容易找到:

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
  @Override
  @Nullable
  protected TransactionAttributeSource getTransactionAttributeSource() {
    return transactionAttributeSource;
  }
};

它创建了一个TransactionAttributeSourcePointcut并在getPointcut()中返回了它,我们继续深入。

@Override
public boolean matches(Method method, @Nullable Class<?> targetClass) {
  if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) {
    return false;
  }
  TransactionAttributeSource tas = getTransactionAttributeSource();
  return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

如果目标类继承了TransactionalProxy,表示该类是事物功能的基础配置类,就不进行增强,此处的逻辑和之前AOP的思路很像。

当然,如果是普通的类的话就尝试获得它方法上事物注解的配置属性,如果有就表示打了@Transactional,就要进行增强,如果没有就不用增强了。【此处在判断时顺手将注解的配置属性进行了存储,一句两得】

其中getTransactionAttributeSource()正是BeanFactoryTransactionAttributeSourceAdvisor中创建匿名类覆盖的方法——返回的是我们整合的transactionAttributeSource,即AnnotationTransactionAttributeSource类型的实例。

我们继续看getTransactionAttribute()

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
  // 只是个Object基础类,不会有注解
  if (method.getDeclaringClass() == Object.class) {
    return null;
  }

  // First, see if we have a cached value.
  // 计算方法对应的唯一key,看之前是否做过解析、缓存
  Object cacheKey = getCacheKey(method, targetClass);
  TransactionAttribute cached = this.attributeCache.get(cacheKey);
  if (cached != null) {
    // 有之前的缓存
    // Value will either be canonical value indicating there is no transaction attribute,
    // or an actual transaction attribute.
    if (cached == NULL_TRANSACTION_ATTRIBUTE) { // 之前的标记是没有
      return null;
    } else {
      return cached;
    }
  } else {
    // 之前没有缓存过,我们需要现场进行解析、缓存、返回
    // We need to work it out.
    TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
    // 缓存并返回
    // Put it in the cache.
    if (txAttr == null) {
      this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
    } else {
      String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
      if (txAttr instanceof DefaultTransactionAttribute) {
        ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
      }
      this.attributeCache.put(cacheKey, txAttr);
    }
    return txAttr;
  }
}

总体来说还是将提取逻辑委托给了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 method may be on an interface, but we need attributes from the target class.
  // If the target class is null, the method will be unchanged.
  // 传入的方法可能是接口的或者父类的,我们要拿到最下层实现类的方法【先找那个上面的打标,找不到再往上找】
  Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

  // First try is the method in the target class.
  // 找到最下层实现类的方法上的 @Transactional 注解属性
  TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
  if (txAttr != null) { // 拿到,直接返回
    return txAttr;
  }

  // Second try is the transaction attribute on the target class.
  // 最下层实现类的方法上没拿到,就看最下层实现类上是否有注解【最终调用的都是一个套路,只是这里给封装成不同的入参而已】
  txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
  if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    return txAttr;
  }

  // 在具体方法、具体类都没找到,就往上层找,如果具体化之后的方法和传入方法不一样就说明可以向上追溯
  if (specificMethod != method) {
    // Fallback is to look at the original method.
    // 找上层方法的注解
    txAttr = findTransactionAttribute(method);
    if (txAttr != null) {
      return txAttr;
    }
    // Last fallback is the class of the original method.
    // 找上层类的注解
    txAttr = findTransactionAttribute(method.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }
  }

  return null;
}

注意:此处的注解提取逻辑比较重要,因为很多时候我们在使用@Transactional时或多或少可能遇到坑;而且,自定义注解和相关实现逻辑时也可借用此处的api

此处介绍一下Spring封装的获得指定元素上注解信息的API:AnnotatedElementUtils.findMergedAnnotationAttributes()

protected TransactionAttribute findTransactionAttribute(Method method) {
  return determineTransactionAttribute(method);
}
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
  // 这里虽然提供配置多个 annotationParser 的选项,我们只关注 Spring 的
  for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
    TransactionAttribute attr = annotationParser.parseTransactionAnnotation(element);
    if (attr != null) {
      return attr;
    }
  }
  return null;
}

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
  // 调用 utils 方法,拿到注解中所有的配置属性
  AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
    element, Transactional.class, false, false);
  // 将配置属性拼装成对应的domain并返回
  if (attributes != null) {
    return parseTransactionAnnotation(attributes);
  } else {
    return null;
  }
}

我们上面的获得注解属性操作都是对AnnotatedElementUtils的封装、使用。对于其具体使用方法不再深追。有需要再看。

其实到此,我们就完成了事物相关操作中在进行AOP增强时对类是否进行增强增强的判断。剩下的增强逻辑AOP会帮我们做。

问题遗留

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容