Transactional和普通自定义切面执行顺序的思考
创始人
2025-05-28 15:23:49
0

前言

因为之前读过spring transaction相关源码《Transaction Management源码阅读路径》,所以对@Transactional和普通自定义切面执行顺序有一定的了解,本篇文章主要通过源码的角度解释下两个切面的执行顺序以及平时开放中需要注意的问题。

  • 平时使用切面去加分布式锁,是先开启事务还是先尝试获得锁?这两者有啥区别?
  • 业务中怎么控制切面的顺序?切面的顺序对事务的影响怎么避免?

普通未指定order的切面和@Transaction的先后顺序

先说下为啥会考虑到这个,我们可以知道@Transaction一般加在具体要执行业务的service方法上,那如果我要进行并发控制对业务进行加锁,那么尝试锁和开启事务孰先孰后呢?按照业务流程上来看我们需要先尝试锁后开启事务,因为没获得锁开启事务需要和数据库进行交互开启一个新的事务,平常对业务结果是不会影响的,但是当高并发时是会对数据库带来不小压力。

但是平时的时候真的有注意和确定两者的顺序吗?下面我们来通过源码来看看。

先定义普通的切面

package com.study.spring.aspect;import com.study.spring.annotation.SimpleAnnotation;
import com.study.spring.service.impl.TestServiceImpl;
import com.study.spring.entity.TestDo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class SimpleAspect {@Autowiredprivate TestServiceImpl testService;@Around("@annotation(simpleAnnotation)")@SneakyThrowspublic Object simpleAround(ProceedingJoinPoint pjp, SimpleAnnotation simpleAnnotation) {log.info("进入simpleAround");Object proceed = pjp.proceed();log.info("完成simpleAround");return proceed;}
}

再在service方法中加上@SimpleAnnotation和@SimpleAnnotation

@Override
@SimpleAnnotation
@Transactional(rollbackFor = Exception.class)
public void test() {log.info("执行业务");TestDo testUpdate = new TestDo();testUpdate.setId(1);testUpdate.setName("执行业");testService.updateById(testUpdate);
}

下面我们直接来看看生成代理时两个切面的顺序。

有两个关键点我们需要注意

  • 在APC中所有拿到的advisors会进行排序,根据order数字越大优先级越低越在数组后面越会先执行。可以看到ExposeInvocationInteceptor在最前面它是一个特殊的advisor是为切面服务的用于暴露invocation,它的order为Integer.MIN优先级最大。
  • 自定义的切面(默认的是Integer.MAX优先级最低)和事务切面(默认的是Integer.MAX优先级最低)优先级是一样的,但是自定义的排在后面会先执行,因为spring扫描的时候会先扫描事务相关的。

下面看下执行顺序:

会先执行事务切面

后面执行自定义切面。

总结

如果普通切面没指定order会比transaction后执行。当锁或者一些检查性切面被使用时如果条件不满足不能进入业务也会导致事务的开启产生了不必要的消耗,当并发高时尤为明显。

如果是synchronized等阻塞性锁还会导致提前创建事务因为mvcc会导致读旧值的情况,并发时会出现问题。

那么我们怎么避免此类影响呢?

切面的顺序对事务的影响怎么避免?

其实避免方式有三种,一种是指定order,一种是把自定义切面移到更外层中,一种是使用编程式事务。

指定order

package com.study.spring.aspect;import com.study.spring.annotation.SimpleAnnotation;
import com.study.spring.service.impl.TestServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component
@Order(1)
public class SimpleAspect {@Autowiredprivate TestServiceImpl testService;@Around("@annotation(simpleAnnotation)")@SneakyThrowspublic Object simpleAround(ProceedingJoinPoint pjp, SimpleAnnotation simpleAnnotation) {log.info("进入simpleAround");Object proceed = pjp.proceed();log.info("完成simpleAround");return proceed;}
}

这里我们指定了@Order(1),下面看看APC中的advisors中的顺序。

再看看实际执行顺序

可以看到自定义的切面先执行了。

移到更外层中

移到更外层中就不用证明了,调用的自然顺序,比如放在Controller的方法上。

使用编程式事务

当然可以,调用的自然顺序,事务的开启更加现式。

总结

因为声明式事务比较好用,生产中使用的比较多,只有为了控制事务粒度或者不需要抽出一个新的类(为了使事务生效)才会使用编程式事务。

所以更加倾向于移到更外层,因为指定order的前提是你知道事务切面的和不指定order普通切面的顺序,同时一旦切面变多比如有统一加锁切面、统一检查是否认证切面等需要控制自定义切面顺序容易和事务切面搞混,不利于维护,这个也相当于自定义切面和框架前面隔离。这也从一个侧面证明了校验放controller的合理性。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...