承接Spring源码情操陶冶-自定义节点的解析。本节关于事务进行简单的解析

spring配置文件样例

简单的事务配置,对save/delete开头的方法加事务,get/find开头的设置为不加事务只读模式

<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>

TxAdviceBeanDefinitionParser解析器

tx:advice节点对应的解析器为TxAdviceBeanDefinitionParser,下面针对该解析器作下详细的解读

实例化对象

直接看复写的getBeanClass()方法

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

TxAdviceBeanDefinitionParser解析器最终解析tx:advice节点为TransactionInterceptor对象

通用的属性集合

	private static final String METHOD_ELEMENT = "method";

	private static final String METHOD_NAME_ATTRIBUTE = "name";

	private static final String ATTRIBUTES_ELEMENT = "attributes";

	private static final String TIMEOUT_ATTRIBUTE = "timeout";

	private static final String READ_ONLY_ATTRIBUTE = "read-only";

	private static final String PROPAGATION_ATTRIBUTE = "propagation";

	private static final String ISOLATION_ATTRIBUTE = "isolation";

	private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for";

	private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for";

针对上述的属性,我们可以看下其中的具体解析

doParse()-解析tx:advice节点

源码端上

	@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// 解析transaction-manager属性对应的bean ref名,默认名为transactionManager
builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element)); // 解析子节点tx:attributes
List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
if (txAttributes.size() > 1) {
parserContext.getReaderContext().error(
"Element <attributes> is allowed at most once inside element <advice>", element);
}
else if (txAttributes.size() == 1) {
// Using attributes source.
Element attributeSourceElement = txAttributes.get(0);
// 解析tx:attribute集合
RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
}
else {
// 注册解析器用于解析注解@Transactional
builder.addPropertyValue("transactionAttributeSource",
new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));
}
}

对于@Transactional方式的解析我们不在此处展开,我们先看下通用的parseAttributeSource()方法解析tx:attribute集合,其会被包装为NameMatchTransactionAttributeSource.class对象。源码如下

private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
// 解析tx:method节点
List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());
transactionAttributeMap.setSource(parserContext.extractSource(attrEle)); //
for (Element methodEle : methods) {
// 解析name属性,其可符合ant-style模式.包装成TypedStringValue对象
String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(methodEle)); // 解析propagation、isolation、timeout、read-only属性
RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
if (StringUtils.hasText(propagation)) {
attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
}
if (StringUtils.hasText(isolation)) {
attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
}
if (StringUtils.hasText(timeout)) {
try {
attribute.setTimeout(Integer.parseInt(timeout));
}
catch (NumberFormatException ex) {
parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);
}
}
if (StringUtils.hasText(readOnly)) {
attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
} // 解析rollback-for、no-rollback-for属性
List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();
if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);
}
if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);
}
attribute.setRollbackRules(rollbackRules); transactionAttributeMap.put(nameHolder, attribute);
} // 最后包装成NameMatchTransactionAttributeSource对象,存放上述的配置
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
return attributeSourceDefinition;
}

代码很简单,都是解析属性的,不过还是对上述的一些配置作下白话的总结


  • name 支持ant-style语法,即匹配对应的方法,比如save*,匹配saveUser()/save()等方法

  • propagation 事务传播方式,对应spring的TransactionDefinition接口类常量

    • required 对应PROPAGATION_REQUIRED,对当前的方法判断如果不存在事务,则创建事务。默认配置
    • required_new 对应PROPAGATION_REQUIRED_NEW,对当前方法判断如果存在事务,则创建新事务,待方法执行完毕后恢复事务;反之创建新事务,让方法运行在新事务环境下。即当前方法将运行在独立的新事务下
    • supports 对应PROPAGATION_SUPPORTS,对当前方法判断如果存在事务,则加入该事务;反之则让方法处于非事务状态执行
    • not_spported 对应PROPAGATION_NOT_SUPPORTED,对当前方法判断如果存在事务,则挂起该事务,等方法执行完毕后,再恢复事务。即当前方法不需要事务支持
    • mandatory 对应PROPAGATION_MANDATORY,对当前方法判断如果存在事务,则加入该事务;反之不能新建事务,且抛出异常。即必须处于事务下运行
    • never 对应PROPAGATION_NEVER,对当前方法判断如果存在事务,则抛异常;反之正常运行。即必须在非事务下运行
    • nested 对应PROPAGATION_NESTED,可嵌入式的事务。
  • isolation 事务隔离级别,对应spring的TransactionDefinition接口类常量

    • default 对应ISOLATION_DEFAULT,不作隔离要求,可能会导致dirty read/unrepeatable read/phantom read
    • read_uncommitted 对应JDBC Connection的TRANSACTION_READ_UNCOMMITTED ,可能会导致dirty read/unrepeatable read/phantom read
    • read_committed 对应JDBC Connection的TRANSACTION_READ_COMMITTED,可能会导致unrepeatable read/phantom read
    • reaptable_read 对应JDBC Connection的TRANSACTION_REPEATABLE_READ,可能会导致phantom read
    • serializable 对应JDBC Connection的TRANSACTION_SERIALIZABLE,最安全但最耗性能

      其中关于脏读、不可重复读、幻读的概念见引文。另附言博主对不可重复读、幻读的理解

      两者均是在同一事务中会出现的情况,执行的条件均一样。但不可重复读关心返回的数据是否一致,而幻读关心返回的数据条数是否一致

  • timeout 超时参数,单位为s。其只应用于事务传播方式为Required/Required_new,默认为-1

  • read-only 是否配置事务只读,默认为false

  • rollback-for 异常回滚策略配置,即出现何种异常进行回滚,可配置多个异常,支持,分隔。注意此处的配置的异常名也符合ant-style模式

  • no-rollback-for 异常不回滚策略配置,即出现何种异常不进行回滚,可配置多个异常,支持,分隔。注意此处的配置的异常名也符合ant-style模式


事务拦截逻辑-TransactionInterceptor

UML一览

通过上图我们发现其也是Advice接口的实现类,说明此类可应用于aop:advisor配置

invoke()-MethodInterceptor公共调用方法

所有的Advisor封装类都会含有MethodInterceptor的实现类的引用,我们可以看下事务处理的切面处理方式

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}

其会调用invokeWithinTransation()方法来解决此类问题,通过表面文字我们可以猜出其会判断对相应的方法是否添加事务来执行,由于代码过长,博主就截取重要的片段来分析

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}

从以上的代码可知,我们可以得到以下结论

  1. 根据method对应的事务配置,创建TransactionInfo对象。即判断是否对相应的方法加上事务
  2. 再执行相应的方法的业务
  3. 如果执行业务过程中,出现异常则根据异常匹配规则进行相应的回滚策略
  4. 无第三点的条件则会保存当前的事务状态
  5. 最后提交事务,使增删改查操作生效,保持一致性、原子性

小结

tx:advice配置多与spring aop结合使用,通过切面的解耦使其可以在方法每次执行的时候根据配置是否添加事务,是个很好的代码设计。

Spring源码情操陶冶-tx:advice解析器的更多相关文章

  1. Spring源码情操陶冶#task:scheduled-tasks解析器

    承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置.备注:此文建立在spring的4.2.3.RELEASE版本 附例 S ...

  2. Spring源码情操陶冶#task:executor解析器

    承接Spring源码情操陶冶-自定义节点的解析.线程池是jdk的一个很重要的概念,在很多的场景都会应用到,多用于处理多任务的并发处理,此处借由spring整合jdk的cocurrent包的方式来进行深 ...

  3. SpringMVC源码情操陶冶-AnnotationDrivenBeanDefinitionParser注解解析器

    mvc:annotation-driven节点的解析器,是springmvc的核心解析器 官方注释 Open Declaration org.springframework.web.servlet.c ...

  4. Spring源码情操陶冶-自定义节点的解析

    本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...

  5. Spring源码情操陶冶-任务定时器ConcurrentTaskScheduler

    承接前文Spring源码情操陶冶#task:scheduled-tasks解析器,本文在前文的基础上讲解单核心线程线程池的工作原理 应用附例 承接前文的例子,如下 <!--define bean ...

  6. Spring源码情操陶冶-AOP之Advice通知类解析与使用

    阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析 入口 根据前文讲解,我们知道通知类的 ...

  7. Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器

    承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过context:component-scan节点干了什么事 ComponentScanBeanDefinitionParse ...

  8. Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器

    本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...

  9. Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器

    本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...

随机推荐

  1. 编程菜鸟的日记-Linux无处不在

    原文来自:http://www.linuxfederation.com/linux-everywhere Linux无处不在 “Linux无处不在.从空间站到微波炉到有Linux.”你可能听说很多以及 ...

  2. golang ntp协议客户端

    NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用来在分布式时间服务器和客户端之间进行时间同步.NTP基于UDP报文进行传输,使用的UDP端口 ...

  3. 实现lodash.get功能

    function deepGet(object, path, defaultValue) { return (!Array.isArray(path) ? path.replace(/\[/g, '. ...

  4. React state和props使用场景

    一个组件的显示状态可以由内部状态state.外部参数props所决定. props: 1.props 是从外部传进组件的参数,主要是父组件向子组件传递数据. 2.props 对于使用它的组件来说是只读 ...

  5. BIO,NIO与AIO的区别

    Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理.Java AIO(NIO.2 ...

  6. [Swift]LeetCode478. 在圆内随机生成点 | Generate Random Point in a Circle

    Given the radius and x-y positions of the center of a circle, write a function randPoint which gener ...

  7. [Swift]LeetCode840. 矩阵中的幻方 | Magic Squares In Grid

    A 3 x 3 magic square is a 3 x 3 grid filled with distinct numbers from 1 to 9 such that each row, co ...

  8. Python的数据库操作(pymysql)

    使用原生SQL语句进行对数据库操作,可完成数据库表的建立和删除,及数据表内容的增删改查操作等.其可操作性很强,如可以直接使用“show databases”.“show tables”等语句进行表格之 ...

  9. 【java爬虫】---爬虫+jsoup轻松爬博客

    爬虫+jsoup轻松爬博客 最近的开发任务主要是爬虫爬新闻信息,这里主要用到技术就是jsoup,jsoup 是一款 Java的HTML解析器,可直接解析某个URL地址.HTML文本内容.它提供了一套非 ...

  10. Qt之自定义托盘(二)

    上一篇文章讲述了自定义Qt托盘,不过不是使用QSystemTrayIcon这个类,而是我们自己完全自定义的一个类,我们只需要处理这个类的鼠标hover.鼠标左键点击.鼠标右键点击和鼠标左键双击,就可以 ...