@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 {
@Autowired
private TestServiceImpl testService;

<span class="hljs-meta">@Around(<span class="hljs-string">"@annotation(simpleAnnotation)"</span>)</span>
<span class="hljs-meta">@SneakyThrows</span>
<span class="hljs-keyword">public</span> Object simpleAround(ProceedingJoinPoint pjp, SimpleAnnotation simpleAnnotation) {
    log.info(<span class="hljs-string">"进入simpleAround"</span>);
    Object proceed = pjp.proceed();
    log.info(<span class="hljs-string">"完成simpleAround"</span>);
    <span class="hljs-keyword">return</span> proceed;
}

}
复制代码

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

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

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

image.png

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

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

下面看下执行顺序:

image.png

会先执行事务切面

image.png 后面执行自定义切面。

总结

如果普通切面没指定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 {
@Autowired
private TestServiceImpl testService;

<span class="hljs-meta">@Around(<span class="hljs-string">"@annotation(simpleAnnotation)"</span>)</span>
<span class="hljs-meta">@SneakyThrows</span>
<span class="hljs-keyword">public</span> Object simpleAround(ProceedingJoinPoint pjp, SimpleAnnotation simpleAnnotation) {
    log.info(<span class="hljs-string">"进入simpleAround"</span>);
    Object proceed = pjp.proceed();
    log.info(<span class="hljs-string">"完成simpleAround"</span>);
    <span class="hljs-keyword">return</span> proceed;
}

}
复制代码

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

image.png

再看看实际执行顺序

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

移到更外层中

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

使用编程式事务

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

总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值