本文首发微信公众号【Java编程探微】
1. 前言
上一节介绍了编程式事务,我们发现,事务操作有两个特点。一是事务操作广泛地出现在应用程序中,二是事务操作的逻辑是有迹可循的,无非是对开启事务、回滚和提交这几个操作的组合使用。换句话说,事务操作是一种公共行为,可以从众多的业务代码中剥离出来,这是很典型的面向切面编程。因此,Spring 依托已有的 AOP 功能,提供了一套使用注解进行事务管理的解决方案,即声明式事务。
声明式事务可以看做是面向切面编程的一个用例,在实际开发中得到了广泛地使用。鉴于此,我们需要先构建一个事务切面,然后才能进一步研究声明式事务是如何工作的。
2. @Transactional 注解
按照惯例,声明式事务也是以注解为切入点。@Transactional
注解的作用是将一个方法标记为事务方法,value
属性很重要,表示事务方法所属的事务管理器。我们知道一个事务管理器对应一个数据源,因此指定 value
属性相当于决定了 SQL 操作指向的数据源。
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
//指定事务管理器,默认为空串,使用默认的事务管理器
String value() default "";
//事务的传播属性,默认为required
Propagation propagation() default Propagation.REQUIRED;
//超时时间
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//是否为只读事务
boolean readOnly() default false;
}
3. AOP 组件
3.1 概述
Spring 没有采用 AspectJ 框架来构成事务切面,而是使用 Spring 自带的 AOP 实现。我们知道,实现 Spring AOP 需要三大组件,Advisor、Pointcut 以及 Interceptor。声明式事务也是建立在 AOP 组件基础之上的,简单介绍如下:
TransactionAttributeSourcePointcut
:寻找一系列符合条件的方法,构建切面TransactionInterceptor
:事务语义的逻辑所在,执行目标方法时进行拦截BeanFactoryTransactionAttributeSourceAdvisor
:将切面与增强逻辑组合起来

3.2 Advisor 组件
BeanFactoryTransactionAttributeSourceAdvisor
持有一个 Pointcut
实例,这里创建了一个匿名的 TransactionAttributeSourcePointcut
对象。也就是说,切点是固定的,只需要指定一个 TransactionAttributeSource
组件来完成构建切面的工作。
java
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
private TransactionAttributeSource transactionAttributeSource;
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut(){
@Override
protected TransactionAttributeSource getTransactionAttributeSource() {
return transactionAttributeSource;
}
};
}
3.3 Pointcut 组件
TransactionAttributeSourcePointcut
是一个抽象类,由 BeanFactoryTransactionAttributeSourceAdvisor
创建了一个匿名的实现类。该类继承了 StaticMethodMatcherPointcut
,说明是对方法的静态编译信息进行匹配 。在 matches
方法中,首先获取 TransactionAttributeSource
对象,然后调用 getTransactionAttribute
方法检查指定方法或类上是否存在事务信息。如果存在,则说明目标方法需要增强。
java
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
//过滤掉TransactionalProxy、PlatformTransactionManager、PersistenceExceptionTranslator
TransactionAttributeSource tas = getTransactionAttributeSource();
return tas == null || tas.getTransactionAttribute(method, targetClass) != null;
}
protected abstract TransactionAttributeSource getTransactionAttributeSource();
}
3.4 Interceptor 组件
TransactionInterceptor
继承了 TransactionAspectSupport
,事务代理对象创建完成之后,每次调用目标方法时,由 TransactionInterceptor
进行拦截。invoke
方法的逻辑并不复杂,invokeWithinTransaction
方法由父类实现,在适当的时机回调 InvocationCallback
匿名对象,然后执行目标方法。
注:本节暂不涉及
invokeWithinTransaction
方法的具体逻辑,事务拦截的具体逻辑将在下节介绍,因此先略过父类TransactionAspectSupport
。
java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
//回调执行目标方法
return invocation.proceed();
}
});
}
}
4. 事务切点
4.1 继承结构
前面介绍了事务切面的三个组件,Advisor 的作用是整合 Pointcut 和 Interceptor,这一点很好理解。Interceptor 的作用是执行事务操作,下节详细介绍。本节的重点是 Pointcut,也就是说,我们要知道一个对象是否需要被代理,或者说如何判断一个方法是事务方法。

事务切点的具体工作是交由 TransactionAttributeSource
处理的。继承结构看似复杂,但分工很明确,可以分为三组:
- 事务信息:包括
TransactionAttribute
接口及其子类,作用是描述声明式事务的相关信息 - 事务注解解析器:包括
TransactionAnnotationParser
接口及其子类,负责对事务相关的注解进行解析 - 事务信息的来源:包括
TransactionAttributeSource
接口及其子类,负责检索声明式事务的相关信息
4.2 TransactionAttribute
TransactionAttribute
扩展了 TransactionDefinition
接口,作用是定义了声明式事务的行为。
getQualifier
方法:为事务方法选择一个事务管理器,如果为空串则使用默认的事务管理器rollbackOn
方法:是否在指定异常上进行回滚操作
java
public interface TransactionAttribute extends TransactionDefinition {
String getQualifier();
boolean rollbackOn(Throwable ex);
}
DefaultTransactionAttribute
不仅是 TransactionAttribute
接口的实现类,还扩展了 DefaultTransactionDefinition
,作用是为 @Transactional
注解提供支持。该类实现了 rollbackOn
方法,对运行时异常和错误进行回滚。问题在于 Exception
有多种实现,为何仅回滚运行时异常?下一节将会详细讨论。
java
public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute{
private String qualifier;
private String descriptor
@Override
public boolean rollbackOn(Throwable ex) {
return ex instanceof RuntimeException || ex instanceof Error;
}
}
4.3 TransactionAttributeSource
TransactionAttributeSource
接口的作用是检索事务信息,getTransactionAttribute
方法用于查找指定方法上的事务属性,如果返回 null 说明是一个普通方法,否则认为是一个事务方法。
java
public interface TransactionAttributeSource {
TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass);
}
AbstractFallbackTransactionAttributeSource
是 TransactionAttributeSource
接口的抽象子类,完成了查找事务信息的主要逻辑。该类有两个特点,首先,由于每次执行拦截逻辑都需要拿到目标方法的事务信息,因此有必要将 TransactionAttribute
缓存起来。其次,在查找事务信息的过程中存在三次降级的过程,具体的逻辑在构建事务切面时详细介绍。
java
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource{
private final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>(1024);
private static final TransactionAttribute NULL_TRANSACTION_ATTRIBUTE = new DefaultTransactionAttribute();
}
AnnotationTransactionAttributeSource
是主要的实现类,从类名中可以看出,该类是通过声明注解的方式提供事务信息的来源。该类持有一个 TransactionAnnotationParser
集合,并在构造函数中添加了一个默认的事务注解解析器,实际的解析工作是由 SpringTransactionAnnotationParser
完成的。
java
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource {
private final Set<TransactionAnnotationParser> annotationParsers;
public AnnotationTransactionAttributeSource() {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
4.4 TransactionAnnotationParser
TransactionAnnotationParser
是解析事务注解的策略接口,该接口有三个主要实现类,用于处理不同的情况,如下所示:
SpringTransactionAnnotationParser
:解析@Transactional
注解,基于 JDBC 事务Ejb3TransactionAnnotationParser
:解析@TransactionAttribute
注解,基于 EJB3 的事务(仅了解)JtaTransactionAnnotationParser
:解析@javax.transaction.Transactional
注解,基于 JTA 分布式事务(仅了解)
java
public interface TransactionAnnotationParser {
TransactionAttribute parseTransactionAnnotation(AnnotatedElement element);
}
5. 构建事务切面
5.1 概述
我们知道,只要一个对象至少存在一个增强方法,那么就应该被代理。TransactionAttributeSourcePointcut
的 matches
方法负责检查指定方法是否满足要求。实际的逻辑是由 TransactionAttributeSource
完成的,接下来我们以 getTransactionAttribute
方法为切入点来分析具体的匹配逻辑。
java
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
//获取方法的事务属性,如果不为 null 则表示匹配
return tas == null || tas.getTransactionAttribute(method, targetClass) != null;
}
}
5.2 代码实现
接下来的操作由三个类完成,因此可以分为三步。第一步,AbstractFallbackTransactionAttributeSource
构建了检索事务信息的基本流程。在 getTransactionAttribute
方法中,先尝试从缓存中获取指定方法的事务信息,然后调用 computeTransactionAttribute
方法,将注解上的信息转译出来。如果返回值不为空,则说明这是一个增强方法。
java
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
@Override
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
Object cacheKey = new MethodClassKey(method, targetClass);
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
//如果等于默认空值说明无事务,返回null
return cached != NULL_TRANSACTION_ATTRIBUTE ? cached : null;
}
//寻找事务信息
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
if (txAttr == null) {
this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}else{
this.attributeCache.put(cacheKey, txAttr);
}
return txAttr;
}
}
在 computeTransactionAttribute
方法执行过程中,优先查找方法上的注解,其次才是类上的注解。如果采用 JDK 动态代理的方式,还需要检查接口方法以及接口上的注解。这样一共会出现四种情况,根据优先级从高到低逐步降级执行,这也是类名中有 fallback 一词的原因所在。
注:Spring 早期版本默认使用 JDK 动态代理,现在则换成了 CGLIB 代理。再者,我们通常将注解声明在方法上,而非类或接口上,这样处理起来更灵活。绝大多数情况下,我们只关心第一种情况,即在实现类的方法上声明注解。
java
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
//如果是Cglib代理,忽略自动生成的的子类,获取原始类
Class<?> userClass = ClassUtils.getUserClass(targetClass);
//如果是JDK代理,method可能是接口定义的方法,获取实现类上的方法
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
//如果存在泛型参数,忽略桥接方法,找到原始的方法
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
//1. 获取目标类的方法上的事务属性
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
//2. 获取目标类上的事务属性(第一次降级)
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null) {
return txAttr;
}
//如果使用JDK代理的方式创建对象,尝试获取接口上的注解
if(method != specificMethod){
//3. 获取接口方法上的事务属性(第二次降级)
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
//4. 获取接口上的事务属性(第三次降级)
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
}
第二步,子类 AnnotationTransactionAttributeSource
重写了两个模板方法 findTransactionAttribute
,作用是寻找指定类(包括接口)或方法上的事务信息。这两个重载方法最终都调用了 determineTransactionAttribute
方法,实际委托给 SpringTransactionAnnotationParser
来处理。
java
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource {
//寻找方法上的注解
@Override
protected TransactionAttribute findTransactionAttribute(Method method) {
return determineTransactionAttribute(method);
}
//寻找类上的注解
@Override
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
return determineTransactionAttribute(clazz);
}
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element){
for (TransactionAnnotationParser parser : this.annotationParsers) {
TransactionAttribute txAttr = parser.parseTransactionAnnotation(element);
if (txAttr != null) {
return txAttr;
}
}
return null;
}
}
第三步,SpringTransactionAnnotationParser
负责解析事务注解。parseTransactionAnnotation
方法的实现比较简单,分为两步。首先获取指定类或方法上的 @Transactional
注解,然后创建 DefaultTransactionAttribute
对象,逐一提取事务注解上的属性。最终返回 TransactionAttribute
对象。
java
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser{
//解析事务注解
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
//寻找方法上的@Transactional
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(element,Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
return null;
}
//解析@Transactional等注解
private TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
DefaultTransactionAttribute txAttr = new DefaultTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
txAttr.setPropagationBehavior(propagation.value());
txAttr.setTimeout(attributes.getNumber("timeout").intValue());
txAttr.setReadOnly(attributes.getBoolean("readOnly"));
txAttr.setQualifier(attributes.getString("value"));
//rollback相关属性,略
return txAttr;
}
}
6. 测试
准备一个配置类 AspectConfig
,通过单例方法向 Spring 容器注册事务切面的相关组件,注意这些方法的顺序是固定的。
- 注册
AnnotationTransactionAttributeSource
,该组件是TransactionAttributeSourcePointcut
的一部分,负责对方法进行匹配。 - 注册
TransactionInterceptor
,实现事务拦截逻辑。 - 注册
BeanFactoryTransactionAttributeSourceAdvisor
,持有一个切点和拦截器。
java
//测试类
@Configuration
@Import(DataSourceConfig.class)
public class AspectConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
return interceptor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
return advisor;
}
}
测试类 MyTransactionInterceptor
用于模拟事务拦截,仅打印日志。为了不影响 TransactionInterceptor
的正常流程,所以使用子类来模拟。
java
//测试类
public class MyTransactionInterceptor extends TransactionInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
System.out.println("调用事务 --> 目标类: " + targetClass.getName() + ", 方法: " + invocation.getMethod().getName());
return invocation.proceed();
}
}
修改业务类 UserService
,定义一个方法并声明 @Transactional
注解。由于尚未实现事务的真正逻辑,仅打印日志表示方法执行了。
java
//测试类
@Service
public class UserService {
@Transactional
public void save(){
System.out.println("UserService save...");
}
}
测试方法分为四步。第一步,创建 Spring 容器,引入 AspectConfig
配置类,注册事务切面的三个组件。第二步,注册 MyTransactionInterceptor
,覆盖掉默认的事务拦截器,便于测试。第三步,由于事务是基于 AOP 功能运行的,因此还要注册 AOP 相关的组件。第四步,刷新容器,获取 UserService
对象,然后调用事务方法。
java
//测试方法
@Test
public void testTransactionAspect(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AspectConfig.class);
//注册MyTransactionInterceptor,覆盖默认的TransactionInterceptor
context.registerBeanDefinition("transactionInterceptor", new RootBeanDefinition(MyTransactionInterceptor.class));
//注册Aop组件
AopConfigUtils.registerAutoProxyCreatorIfNecessary(context);
context.refresh();
UserService userService = context.getBean("userService", UserService.class);
userService.save();
}
从测试结果可以看到,MyTransactionInterceptor
先进行了拦截,然后打印目标方法中的日志,这说明事务 AOP 构建成功。
lua
调用事务 --> 目标类: tx.common.UserService, 方法: save
UserService save...
7. 总结
本节介绍了构建事务切面的三大组件,即 Advisor、Interceptor 和 Pointcut。其中 Advisor 是一个复合类,持有一个拦截器和一个切点。Interceptor 则涉及具体的事务操作,暂不介绍。因此本节的重点是 Pointcut 是如何起作用的,即判断一个类是否应该被代理,或者说一个方法是否被认定为事务方法。
具体来说,TransactionAttributeSourcePointcut
将检索事务注解信息的工作交给 TransactionAttributeSource
组件完成,TransactionAttributeSource
通过降级的方式依次对目标方法、类、接口方法、接口进行检查,如果声明了 @Transactional
注解,则说明这是一个(或一组)事务方法。而事务方法是通过代理对象来实现增强逻辑的,因此声明式事务是 AOP 的一个用例。
8. 项目信息
新增修改一览,新增(16),修改(1)。
scss
tx
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.spring.transaction
│ ├─ annotation
│ │ ├─ AnnotationTransactionAttributeSource.java (+)
│ │ ├─ SpringTransactionAnnotationParser.java (+)
│ │ ├─ Propagation.java (+)
│ │ ├─ Transactional.java (+)
│ │ └─ TransactionAnnotationParser.java (+)
│ └─ interceptor
│ ├─ AbstractFallbackTransactionAttributeSource.java (+)
│ ├─ BeanFactoryTransactionAttributeSourceAdvisor.java (+)
│ ├─ DefaultTransactionAttribute.java (+)
│ ├─ TransactionAspectSupport.java (+)
│ ├─ TransactionAttribute.java (+)
│ ├─ TransactionAttributeSource.java (+)
│ ├─ TransactionAttributeSourcePointcut.java (+)
│ └─ TransactionInterceptor.java (+)
└─ test
└─ java
└─ tx
├─ aop
│ ├─ AspectConfig.java (+)
│ ├─ MyTransactionInterceptor.java (+)
│ └─ TxAopTest.java (+)
└─ common
└─ UserService.java (*)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。