事务拦截器TransactionInterceptor与@Transactional
确定切面的先后顺序,然后在Spring的调度下完美合作,共同增强业务方法,而业务方法本身对此一无所知——这就是AOP的魅力。在Spirng架构中,如果不想使用 @Transactional注解(有时候会忘记,处写这个注解太麻烦),我们就可以。现在,我们用两种风格来实现上述的“打印日志”切面(在烹饪方法前打印)。看完这个类,可以回过头再看一下上面的事务拦截器AOP。这是Spring框架内更原生的编程
事务拦截器TransactionInterceptor与@Transactional
看完本篇推荐看一下这两篇:
- 事务拦截器TransactionInterceptor、事务管理器和DataSource数据源三者的关系
- 事务模板TransactionTemplate、事务管理器和DataSource数据源三者的关系,本篇最后会总结【
使用spring事务的三种方式】。
1. 事务拦截器项目配置
在Spirng架构中,如果不想使用 @Transactional注解(有时候会忘记,处写这个注解太麻烦),我们就可以 使用@Aspect配置式AOP 来装配 事务拦截器(TransactionInterceptor)的方案,完全摆脱@Transactional注解。这是Spring框架内更原生的编程式事务管理方式。
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Configuration
public class TransactionConfig {
// 1. 定义切入点(拦截 com.pro.service 包下所有方法)
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.sh.service..*.*(..))";
// 2. 定义增删改方法前缀(自动加事务)
private static final String[] REQUIRED_RULE_TRANSACTION = {"insert*", "create*", "add*", "save*", "update*", "modify*", "del*", "delete*", "remove*"};
// 3. 定义查询方法前缀(自动加只读事务)
private static final String[] READ_RULE_TRANSACTION = {"select*", "get*", "query*", "search*", "count*", "find*", "list*", "page*"};
// 4. 注入事务管理器
@Autowired
private PlatformTransactionManager transactionManager;
// ========== 5. 核心:配置事务拦截器 (TransactionInterceptor)【这就是个Adivce(通知)】
@Bean
public TransactionInterceptor txAdvice() {
// 5.1 创建事务属性源
NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
// 5.2 配置增删改事务属性(REQUIRED)
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class))); // 发生异常回滚
requiredTx.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); // 事务隔离级别:读已提交
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事务传播行为:REQUIRED
requiredTx.setTimeout(30); // 超时30秒
// 5.3 配置查询事务属性(只读)
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
readOnlyTx.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); // 支持当前事务,不存在也不新建
readOnlyTx.setReadOnly(true); // 关键:设置为只读
readOnlyTx.setTimeout(20); // 查询超时可设短些
// 5.4 将方法名模式映射到事务属性
Map<String, TransactionAttribute> txMap = new HashMap<>();
for (String methodName : REQUIRED_RULE_TRANSACTION) {
txMap.put(methodName, requiredTx);
}
for (String methodName : READ_RULE_TRANSACTION) {
txMap.put(methodName, readOnlyTx);
}
tas.setNameMap(txMap);
// 5.5 创建并返回事务拦截器
return new TransactionInterceptor(transactionManager, tas);
}
// ========== 6. 核心:配置切面(Advisor),将切入点和拦截器关联
@Bean
public Advisor txAdviceAdvisor() {
// 6.1 创建切入点(使用AspectJ表达式)
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
// 6.2 创建 Advisor(通知器),将切入点 和 事务通知 绑定
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
在了解AOP之前先看一张图:
2. 切面
这一小节看一下就行,重要的在下面的第3小节。
用一个生活化的例子——“餐厅点餐服务” 解释AOP的相关概念。
2.1 核心故事:一份油泼面🍽️的旅程
想象你去一家面馆点了一份油泼面。它的核心流程是:
你点餐 → 厨师烹饪 → 你享用。
现在,面馆为了提升体验和管理,在核心流程前后加入了一些“通用服务”:
- 点餐前:服务员确认你是VIP会员,以决定是否赠送餐前冰封(饮料)。
- 点餐后、烹饪前:系统自动打印订单日志到后厨。
- 烹饪后、上桌前:厨师检查面有没有煮熟。
- 你吃完后:服务员邀请你进行满意度评价。
核心思想:煮面是 “核心业务逻辑” ,而身份验证、日志、检查、评价这些都是 “横切关注点” 。AOP的作用,就是在不改动“煮面”这段核心代码的情况下,优雅地插入这些通用服务。
2.2 AOP核心概念📚
让我们把这个故事映射到AOP的各个概念上:
| AOP 概念 | 专业解释 | 面馆通俗理解 |
|---|---|---|
| 连接点 (Join Point) | 程序执行过程中一个明确的点,如方法调用、异常抛出等。 | 流程中所有可以插入服务的节点。例如:点餐()、烹饪()、上菜() 这些方法 被调用的【时刻】。 |
| 切点 (Pointcut) | 一个表达式,用来匹配和筛选你感兴趣的连接点。 | 选择器。例如:“匹配所有 点餐() 方法”,或“匹配所有以 process 开头的方法”。它决定了 “通用服务” 要在 哪里 生效。 |
| 通知 (Advice) | 在切点处执行的动作代码本身。 | 通用服务 的具体内容。例如:“验证VIP身份”、“打印日志”这段代码逻辑。 |
| 切面 (Aspect) | 切点 + 通知 的完整结合体。它定义了“在何处(切点)执行何种操作(通知)”。 |
一个完整的服务方案。例如:“在点餐()方法执行前(切点),执行验证VIP()操作(通知)”,这就是一个完整的切面。 |
| @Aspect | 一个注解,用来把一个普通的Java类声明为一个切面类。 | 给一个服务方案团队(Java类)挂上牌子,上面写着“我们是提供横切服务的”。 |
| @Before | 一种通知类型注解,表示该通知在目标方法执行之前运行。 | 前置服务。例如:“在厨师烹饪之前,先打印订单日志”。 |
| Advisor | Spring AOP中更原始、更底层的切面表示。一个Advisor通常只包含 一个通知 和 一个切点 。 |
一个最小化的、不可分割的服务指令。好比一张工单,严格写着:“仅当客户点油泼面时(切点),才在烹饪后做熟度检查(通知)”。它非常精准、直接。 |
2.3 两种代码💻风格实现“餐厅切面”
现在,我们用两种风格来实现上述的“打印日志”切面(在烹饪方法前打印)。
风格一:基于@Aspect注解(现代、声明式、更常用)
这种方式像用高级语言描述需求。
// 1. 声明这是一个切面类,同时它也是一个Spring管理的Bean
@Component
@Aspect
public class RestaurantAspect {
// 2. 定义切点:匹配所有在 CookingService 类中的 cook 方法
@Pointcut("execution(* com.example.service.CookingService.cook(..))")
public void cookMethod() {}
// 3. 定义通知,并与切点绑定:在烹饪方法执行前
@Before("cookMethod()")
public void printOrderLogBeforeCooking() {
System.out.println("[日志] 后厨收到订单,开始烹饪...");
// 这里可以拿到请求参数、方法名等信息,实现复杂逻辑
}
// 其他通知
@AfterReturning("cookMethod()")
public void checkSteakDoneness() {
System.out.println("[质检] 检查油泼面熟了没...");
}
}
看完这个类,可以回过头再看一下上面的事务拦截器AOP。
通俗理解:你写了一个服务手册(RestaurantAspect),在里面用标签(@Before)写明:“在做饭这个环节之前,要执行打印日志这个动作”。Spring 看到@Aspect这个牌子后,会自动帮你安排。
风格二:基于Advisor编程式配置(底层、灵活、更精确)
这种方式更像直接编写部署指令。
@Configuration
public class RestaurantAdvisorConfig {
@Bean
public Advisor cookingLogAdvisor() {
// 1. 创建通知(Advice):定义要做什么
MethodBeforeAdvice logAdvice = new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("[Advisor日志] 准备烹饪食材: " + args[0]);
}
};
// 2. 创建切点(Pointcut):定义在哪里做(精确到方法名匹配)
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("cook"); // 只匹配名为 "cook" 的方法
// 也可以用 pointcut.setClassFilter() 来限制类
// 3. 将通知和切点组装成最小的切面单元:Advisor
return new DefaultPointcutAdvisor(pointcut, logAdvice);
}
}
通俗理解:你直接发了一张精确的工单(Advisor)给系统。工单上明确指令:“听着,只有当一个方法名严格叫 cook 时(切点),才在它之前执行这个打印动作(通知)。” 这个工单自成一体,非常精准。
核心关系与选择🔄
@Aspect 和 Advisor 是什么关系?
- 最终效果一致:在运行时,Spring都会把
@Aspect注解的切面转换成一个或多个内部的Advisor去执行。 - 抽象层级不同:
@Aspect是声明式的,像写配置清单,更符合人类思维,一个类里可以定义很多相关通知。Advisor是编程式的API,是Spring AOP的工作基石,更底层、更灵活,一个Advisor通常只做一件事。 - 为什么两种都存在?
@Aspect更简洁易用,涵盖了90%的场景。但当需要极其精细的控制(比如根据复杂条件动态决定是否应用通知),或者与一些旧框架集成时,直接使用Advisor这个底层API会更强大。
我们可以通过@Order确定切面的先后顺序,然后在Spring的调度下完美合作,共同增强业务方法,而业务方法本身对此一无所知——这就是AOP的魅力。
3. @Transactional
3.1 @Transactional解释
- “定义一个Advisor绑定拦截器” 是一种编程式、集中配置的方式。
- 使用 @Transactional 注解,是另一种声明式、分散标注的方式。
但两者的终点完全相同:都是为了让 TransactionInterceptor 来拦截方法并管理事务。
当你使用 @Transactional 注解时,Spring Boot在启动阶段通过自动配置(TransactionAutoConfiguration)完成了一系列幕后工作,其本质是自动创建了所需的Advisor。在spring中有这样一个源码:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ImportRuntimeHints(TransactionRuntimeHints.class)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor; // 最终还是一个Advisor!
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// 创建一个默认的事务拦截器
// 这里的入参原本是 TransactionAttributeSource,为了便于理解修改为 AnnotationTransactionAttributeSource
public TransactionInterceptor transactionInterceptor(AnnotationTransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
整个流程可以概括为:@Transactional 注解 → 被 AnnotationTransactionAttributeSource 解析为规则 → 与 TransactionInterceptor 一起被包装进 BeanFactoryTransactionAttributeSourceAdvisor → 该Advisor被应用到所有Bean的创建过程中。
3.2 两种方式的直观对比
为了让区别更清晰,我们可以用下面的表格来对比这两种方式:
| 方面 | 编程式Advisor | @Transactional方式(声明式) |
|---|---|---|
| 配置核心 | 在@Configuration类中,显式定义一个Advisor Bean。 |
在方法或类上添加 @Transactional 注解。 |
| 规则定义 | 在NameMatchTransactionAttributeSource中,通过方法名模式匹配来定义规则。 |
在注解的属性中直接定义规则(如@Transactional(readOnly = true))。 |
| 切点(Pointcut) | 显式指定,如 execution(* com.pro.service..*.*(..))。 |
隐式生成,切点逻辑是“匹配所有有@Transactional注解的地方”。 |
| 底层实现 | 直接操控了Spring AOP最底层的 Advisor、Advice、Pointcut 三大件。 |
利用了Spring的自动配置和@EnableTransactionManagement,在底层帮我们创建了Advisor。 |
| 优点 | 集中管理,规则一目了然;与业务代码完全解耦;适合基于方法名的批量规则。 | 使用简单,贴近业务逻辑;粒度更细,可以针对单个方法特殊配置;是Spring生态的标准做法。 |
| 缺点 | 不够灵活,如果某个方法需要例外规则(如REQUIRES_NEW)很难单独配置。 |
注解分散在代码各处;对业务代码有侵入性;无法根据方法名等动态规则批量处理。 |
5. 结论与选择💡
TransactionInterceptor始终在工作:无论用哪种方式,最终都是TransactionInterceptor这个 “事务引擎” 在执行拦截和管理。区别只在于 “启动引擎的开关” 是 编程式装配的Advisor,还是 由注解触发的自动装配Advisor。- 可以混合使用(但需谨慎):Spring允许两者共存。如果绝大多数方法遵循统一规则,但极个别方法需要特殊事务行为(比如一个特殊的
handlePayment方法需要REQUIRES_NEW),你可以在那个方法上单独添加@Transactional(propagation = Propagation.REQUIRES_NEW)。 - 编程式配置的规则是全局的,而
@Transactional注解的规则是局部的,局部注解通常会覆盖全局规则。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)