文档说明
本文主要针对同类,不同类方法之间的异步调用详解。
本文基于Spring @Async 异步调用四种场景教学文档。
1.所有入口方法 testMain() 均添加 @Transactional 事务;
2.区分:异步逻辑是否和主方法同一事务、是否跟随主方法回滚;
3.核心前置知识点:@Async 基于 AOP 代理生效,同类this调用失效;afterCommit 事务提交后才执行回调逻辑;
4.前置配置:启动类添加 @EnableAsync 开启异步支持。
场景一:A 类 testMain 事务内,异步调用外部 B 类 testB1
1. 主业务类 ClassA
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @Service public class ClassA { @Resource private ClassB classB; /** * 主入口方法,开启事务 */ @Transactional public void testMain() { // 1. 主线务数据库操作(和testMain同事务) // 2. 异步调用外部B类方法 classB.testB1(); // 3. 其余业务操作 // 主线程仅将异步任务提交线程池,无需阻塞等待异步逻辑执行完毕,继续向下执行业务代码。 } }2.外部异步类 ClassB
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class ClassB { /** * 异步业务方法 * 独立线程、独立事务 */ @Async @Transactional public void testB1() { // 数据库操作、耗时业务逻辑 } }3.事务回滚说明
若testMain中的业务于第一步的代码发生异常回滚,testB1不会随testMain一起回滚,testB1没执行。
若testMain中的业务于第三步的代码发生了异常回滚,则testB1已经执行了。
testB1 内部报错:testMain事务不受影响,testMain数据正常提交。因为@Async相当于开启了一个全新的线程,两个之间互不影响。
场景二、A 类 testMain 事务内,afterCommit 回调中异步调用外部 B 类 testB1
1. 主业务类 ClassA
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.Resource; @Service public class ClassA { @Resource private ClassB classB; @Transactional public void testMain() { // 主线务数据库操作 // 注册事务提交后回调 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { // 主线事务完全提交落库后,才异步执行 classB.testB1(); } }); } }2.外部异步类 ClassB
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class ClassB { /** * 异步业务方法 * 独立线程、独立事务 */ @Async @Transactional public void testB1() { // 数据库操作、耗时业务逻辑 } }3.事务回滚说明
若testMain中的业务代码发生异常回滚,testB1压根不会执行,因为afterCommit 回调仅在主线事务完整提交成功后才执行;主线事务回滚时,回调逻辑完全不执行。
testB1 内部报错:testMain事务不受影响,testMain数据正常提交。
场景三、A 类 testMain 事务内,异步调用本类 3 个方法
每次 getBean 获取代理调用本类方法,直接用this调用会使异步注解失效。
针对多个异步本类方法的调用,建议不用获取getBean的方式调用,频繁通过 getBean 获取代理对象存在重复容器查询开销,多异步方法时不利于统一管控线程池参数。建议使用线程池管理调用。
1. 主业务类ClassA
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ClassA implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Transactional public void testMain() { // 1.主线务数据库操作 // 2.每次通过容器获取代理对象,调用本类异步方法 ClassA proxy = applicationContext.getBean(ClassA.class); proxy.testSub1(); proxy.testSub2(); proxy.testSub3(); // 3.其余业务操作 } @Async @Transactional public void testSub1() { // 异步业务1 } @Async @Transactional public void testSub2() { // 异步业务2 } @Async @Transactional public void testSub3() { // 异步业务3 } }2. 事务回滚说明
若testMain中的业务于第一步的代码发生异常回滚,testSub不会随testMain一起回滚,testSub没执行。
若testMain中的业务于第三步的代码发生了异常回滚,testSub已经执行了。
testB1 内部报错:testMain事务不受影响,testMain数据正常提交。因为@Async相当于开启了一个全新的线程,两个之间互不影响。
场景四、A 类 testMain 事务内,afterCommit 回调中异步调用本类 3 个方法
1. 主业务类ClassA
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; @Service public class ClassA implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Transactional public void testMain() { // 主线务数据库操作 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { // 事务提交后,获取代理执行本类异步方法 ClassA proxy = applicationContext.getBean(ClassA.class); proxy.testSub1(); proxy.testSub2(); proxy.testSub3(); } }); } @Async @Transactional public void testSub1() {} @Async @Transactional public void testSub2() {} @Async @Transactional public void testSub3() {} }2. 事务回滚说明
若testMain中的业务代码发生异常回滚,testSub压根不会执行,因为afterCommit 回调仅在主线事务完整提交成功后才执行;主线事务回滚时,回调逻辑完全不执行。
testSub 内部报错:testMain事务不受影响,testMain数据正常提交。
总结说明
| 场景 | 执行时机 | 主线回滚后异步是否执行 | 异步报错是否回滚主线 |
|---|---|---|---|
| 场景 1 直接异步调用外部类 | 主线执行中并行触发 | 可能执行 | 不回滚主线 |
| 场景 2 afterCommit 异步外部类 | 主线事务提交后触发 | 不会执行 | 不回滚主线 |
| 场景 3 直接异步调用本类 (getBean) | 主线执行中并行触发 | 可能执行 | 不回滚主线 |
| 场景 4 afterCommit 异步调用本类 (getBean) | 主线事务提交后触发 | 不会执行 | 不回滚主线 |
使用建议
1.若有数据库操作一类的建议使用场景2
2.针对多个异步本类方法的调用,建议不用获取getBean的方式调用,频繁通过 getBean 获取代理对象存在重复容器查询开销,多异步方法时不利于统一管控线程池参数。建议使用线程池管理调用。