Spring異步編程 | 你的@Async就真的異步嗎?異步歷險奇遇記
點擊上方“java進階架構師”,選擇右上角“置頂公衆號”
20大進階架構專題每日送達
引言有點長
前端的寶寶會用ajax,用異步編程到快樂的不行~ 我們java也有異步,用起來比他們還快樂~ 我們biaji一個注(gǒupí)解(gāoyào),也是快樂風男...
且看下面的栗子:
註冊一個用戶,給他的賬戶初始化積分(也可以想象成註冊獎勵),再給用戶發個註冊通知短信,再發個郵件,(只是舉栗子,切莫牛角大法),這樣一個流程,短信和郵件我覺得完全可以拆分出來,沒必要拖在在主流程上來(再補充上事務[ACID:原子性,一致性,隔離性,持久性]就好了):
今天就這點業務,我在暗想,這不是一個註解就搞掂的嘛~~~ 哈哈哈
結果不是想象中的那麼完美~~ 來看我的異(dind)步(ding)歷險記
我的首發原創博客地址:你的@Async就真的異步嗎 ☞ 異步歷險奇遇記[1]
https://juejin.im/post/5d47a80a6fb9a06ad3470f9a 裏面有gitHub項目地址,關注我,裏面實戰多多哦~
奇遇一 循環依賴異常
看code:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserService userService;
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
userService.senMsg(user);
userService.senEmail(user);
return insert;
}
@Async
@Override
public Boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
public Boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
結果:啓動不起來,Spring循環依賴問題。 Spring不是解決了循環依賴問題嗎,它是支持循環依賴的呀?怎麼會呢?
不可否認,在這之前我也是這麼堅信的,倘若你目前也和我有一樣堅挺的想法,那就讓異常UnsatisfiedDependencyException,has been injected into other beans [userServiceImpl] in its raw version as part of a circular reference,,來鼓勵你,擁抱你, 就是這麼的不給面子,赤裸裸的circular reference。
談到Spring Bean的循環依賴,有的小夥伴可能比較陌生,畢竟開發過程中好像對循環依賴這個概念無感知。其實不然,你有這種錯覺,那是因爲你工作在Spring的襁褓中,從而讓你“高枕無憂”~ 其實我們的代碼中肯定被我們寫了循環依賴,比如像這樣:
@Service
public class AServiceImpl implements AService {
@Autowired
private BService bService;
...
}
@Service
public class BServiceImpl implements BService {
@Autowired
private AService aService;
...
}
通過實驗總結出,出現使用@Async導致循環依賴問題的必要條件:
已開啓@EnableAsync的支持
@Async註解所在的Bean被循環依賴了
奇遇二 異步失效異常
那麼既然不能循環依賴,我們就不循環依賴,我們這麼來:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
SendService sendService;
@Override
@Transactional()
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
this.senMsg(user);
this.senEmail(user);
return insert;
}
@Async
@Override
public Boolean senMsg(User user) {
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
public Boolean senEmail(User user) {
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
結果我們測試了幾把,我打印一下結果:
2019-08-05 21:59:32.304 INFO 14360 --- [nio-8080-exec-3] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2019-08-05 21:59:32.346 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Preparing: insert into t_user (username, sex, mobile,email) values (?, ?, ?,?)
2019-08-05 21:59:32.454 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : ==> Parameters: 王麻子(String), 男(String), 18820158833(String), [email protected](String)
2019-08-05 21:59:32.463 DEBUG 14360 --- [nio-8080-exec-3] c.b.lea.mybot.mapper.UserMapper.insert : <== Updates: 1
User 保存用戶成功:User(id=101, username=王麻子, mobile=18820158833, [email protected], sex=男, password=123435, createTime=Mon Aug 05 12:20:51 CST 2019, updateTime=null)
發送短信中:.....
http-nio-8080-exec-3給用戶id:101,手機號:18820158833發送短信成功
發送郵件中:.....
http-nio-8080-exec-3給用戶id:101,郵箱:[email protected]發送郵件成功
這不白瞎了嗎?感知不到我的愛,白寫了,難受~~線程依然是http-nio-8080-exec-3,那麼爲什麼了呢? 下面會講的哦,先說結論:
通過實驗總結出,出現使用@Async導致異步失效的原因:
在本類中使用了異步是不支持異步的
調用者其實是this,是當前對象,不是真正的代理對象userService,spring無法截獲這個方法調用 所以不在不在本類中去調用,網上的解決方法有applicationContext.getBean(UserService.class)和AopContext.currentProxy()
奇遇三 事務失效異常
那麼,既然不能用當前對象,那我們用代理,AopContext.currentProxy(),然鵝,你發現,報錯了,對Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.就他:
但是你去網上百度就會發現,都這麼搞
@EnableAspectJAutoProxy(exposeProxy = true)
我也這麼搞,但是又報錯了,細細的看報錯內容,才發現少了個jar包這東西要配合切面織入,要配合,懂嗎?,來上包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
再來看爲撒: 這是一個性感的話題,exposeProxy = true它的作用就是啓用切面的自動代理,說人話就是暴露當前代理對象到當前線程綁定, 看個報錯Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.就是AopContext搞得鬼.
public final class AopContext {
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
private AopContext() {
}
// 該方法是public static方法,說明可以被任意類進行調用
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
// 它拋出異常的原因是當前線程並沒有綁定對象
// 而給線程綁定對象的方法在下面:特別有意思的是它的訪問權限是default級別,也就是說只能Spring內部去調用
if (proxy == null) {
throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
// 它最有意思的地方是它的訪問權限是default的,表示只能給Spring內部去調用
// 調用它的類有CglibAopProxy和JdkDynamicAopProxy
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
} else {
currentProxy.remove();
}
return old;
}
}
所以我們要做啓用代理設置,讓代理生效,來走起,主線程的方法使用來調用異步方法,來測試走起: no code said niao:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.senMsg(user);
currentProxy.senEmail(user);
int i = 1 / 0;
return insert;
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senMsg(User user) {
user.setUsername(Thread.currentThread().getName()+"發短信測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senEmail(User user) {
user.setUsername("發郵件測試事務...."+ new Random().nextInt());
userMapper.insert(user);
int i = 1 / 0;
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
}
}
測試結果:
如我們所願,事務和異步!都生效了
1. 異步線程SimpleAsyncTaskExecutor-1和SimpleAsyncTaskExecutor-2分別發短信和郵件,主線程保存用戶
2. 實際結果,主線程保存的那個用戶失敗,名字叫'發送短信'的也保存失敗,只有叫'發郵件'的用戶插入成功
3. 那麼就做到了事務的線程隔離,事務的互不影響,完美
4. 親,你這麼寫了嗎?這麼寫不優美,雖然有用,但是寫的另闢蹊徑啊
奇遇四 異步嵌套異常
來,我們看個更騷氣的,異步中嵌套異步,來上code:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.send(user);
return insert;
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void send(User user) {
//發短信
user.setUsername(Thread.currentThread().getName()+"發短信測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
//發郵件
UserService currentProxy = UserService.class.cast(AopContext.currentProxy());
currentProxy.senEmail(user);
}
@Async @Override @Transactional(propagation = Propagation.REQUIRES_NEW)
public void senEmail(User user) {
user.setUsername("發郵件測試事務...."+ new Random().nextInt());
userMapper.insert(user);
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
}
}
看我們猜下結果? 數據庫會新增幾個數據?3個?2個?1個?0個?納尼報錯?
哈哈``` 上結果:
答案:只有一條數據主線程保存成功,短信和郵件的插入都失敗了,最主要的是還報錯了,又是那個~~~Set 'exposeProxy' property on Advised to 'true'磨人的小妖精
通過實驗總結出,出現導致異步嵌套使用失敗的原因:
在本類中使用了異步嵌套異步是不支持的
調用者其實被代理過一次了,再嵌套會出現'二次代理',其實是達不到代理了效果,因爲已經異步了.即時你在嵌套中不使用代理去獲取,即使不保存,但是事務和異步效果都會跟隨當前的代理,即嵌套的效果是達不到再次異步的.
解決辦法應該有,但是我覺得我還沒找到.這個寫法是我們應該規避的,我們應該遵循規範,啓用新的服務類去完成我們的異步工作
下面我們舉個栗子:正確的寫法,優雅的寫法
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
SendService sendService;
@Override@Transactional(propagation = Propagation.REQUIRED)
public int save(UserDTO userDTO) {
User user = new User();
BeanCopyUtils.copy(userDTO, user);
int insert = userMapper.insert(user);
System.out.println("User 保存用戶成功:" + user);
sendService.senMsg(user);
sendService.senEmail(user);
return insert;
}
}
---------------無責任分割線--------------------
@Service
public class SendServiceImpl implements SendService {
@Override
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 1 / 0;
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
}
public abstract class AopConfigUtils {
...正在加(sheng)載(lue)代碼中 請稍後....
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
}
看到此註解標註的屬性值最終都被設置到了internalAutoProxyCreator身上,也就是它:自動代理創建器。 首先我們需要明晰的是:@Async的代理對象並不是由自動代理創建器來創建的,而是由AsyncAnnotationBeanPostProcessor一個單純的BeanPostProcessor實現的,很顯然當執行AopContext.currentProxy()這句代碼的時候報錯了。@EnableAsync給容器注入的是AsyncAnnotationBeanPostProcessor,它用於給@Async生成代理,但是它僅僅是個BeanPostProcessor並不屬於自動代理創建器,因此exposeProxy = true對它無效。 所以AopContext.setCurrentProxy(proxy);這個set方法肯定就不會執行,所以,因此,但凡只要業務方法中調用AopContext.currentProxy()方法就鐵定拋異常~~
奇遇五 基本類型異常
看嘛,發短信其實是一些網關調用,我想寫個看短信,郵件發送成功的標誌,是否調用成功的狀態,來走起
....省略...UserService
---------------無責任分割線--------------------
@Service
public class SendServiceImpl implements SendService {
@Override
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean senMsg(User user) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("發送短信中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",手機號:" + user.getMobile() + "發送短信成功");
return true;
}
@Async
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean senEmail(User user) {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("發送郵件中:.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "給用戶id:" + user.getId() + ",郵箱:" + user.getEmail() + "發送郵件成功");
return true;
}
}
瞪大眼睛看,我的返回結果是boolean,屬於基本類型,雖然沒有用,但是報錯了:
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for:
public boolean com.boot.lea.mybot.service.impl.SendServiceImpl.senMsg(com.boot.lea.mybot.entity.User)
導致我的數據庫一條數據都沒有,影響到主線程了,可見問題發生在主線程觸發異步線程的時候,那我們找原因: 是走代理觸發的:我先找這個類CglibAopProxy再順藤摸瓜
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
*/
private static Object proce***eturnType(Object proxy, Object target, Method method, Object retVal) {
// Massage return value if necessary
if (retVal != null && retVal == target &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this". Note that we can't help
// if the target sets a reference to itself in another returned object.
retVal = proxy;
}
Class<?> returnType = method.getReturnType();
if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
在這retVal == null && returnType != Void.TYPE && returnType.isPrimitive(),因爲我們的這種異步其實是不支持友好的返回結果的,我們的結果應該是void,因爲這個異步線程被主線程觸發後其實被當做一個任務提交到Spring的異步的一個線程池中進行異步的處理任務了,線程之間的通信是不能之間返回的,其實用這種寫法我們就應該用void去異步執行,不要有返回值,而且我們的返回值是isPrimitive(),是基本類型,剛好達標....
那麼我大聲喊出,使用異步的時候儘量不要有返回值,實在要有你也不能用基本類型.
奇遇六 返回異步結果
有些人就是難受,就是想要返回結果,那麼也是可以滴:但是要藉助Furtrue小姐姐的get()來進行線程之間的阻塞通信,畢竟小姐姐⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄害羞.
醬紫寫,你就可以阻塞等到執行任務有結果的時候去獲取真正的結果了,這個寫法和我之前的文章JAVA併發異步編程 原來十個接口的活現在只需要一個接口就搞定![2]是一樣的道理了
import com.boot.lea.mybot.service.AsyncService;
import com.boot.lea.mybot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
@Service
@Async("taskExecutor")
public class AsyncServiceImpl implements AsyncService {
@Autowired
private UserService userService;
@Override
public Future<Long> queryUserMsgCount(final Long userId) {
System.out.println("當前線程:" + Thread.currentThread().getName() + "=-=====queryUserMsgCount");
long countByUserId = userService.countMsgCountByUserId(userId);
return new AsyncResult<>(countByUserId);
}
@Override
public Future<Long> queryCollectCount(final Long userId) {
System.out.println("當前線程:" + Thread.currentThread().getName() + "=-====queryCollectCount");
long collectCount = userService.countCollectCountByUserId(userId);
return new AsyncResult<>(collectCount);
}
你需要知道的九大異步注意事項
儘量不要在本類中異步調用br/>儘量不要有返回值
不能使用本類的私有方法或者非接口化加註@Async,因爲代理不到失效
異步方法不能使用static修飾
br/>異步類沒有使用@Component註解(或其他註解)導致spring無法掃描到異步類
類中需要使用@Autowired或@Resource等註解自動注入,不能自己手動new對象
br/>如果使用SpringBoot框架必須在啓動類中增加@EnableAsync註解
在調用Async方法的方法上標註@Transactional是管理調用方法的事務的
在Async方法上標註@Transactional是管理異步方法的事務,事務因線程隔離
你需要懂的異步原理
@Async的異步:
- 實際是spring 在掃描bean的時候會掃描方法上是否包含@Async的註解,如果包含的,spring會爲這個bean動態的生成一個子類,我們稱之爲代理類(jdkProxy), 代理類是繼承我們所寫的bean的,然後把代理類注入進來,那此時,在執行此方法的時候,會到代理類中,代理類判斷了此方法需要異步執行,就不會調用父類 (我們原本寫的bean)的對應方法。
- spring自己維護了一個隊列,他會把需要執行的方法,放入隊列中,等待線程池去讀取這個隊列,完成方法的執行, 從而完成了異步的功能。
- 我們可以關注到再配置task的時候,是有參數讓我們配置線程池的數量的。因爲這種實現方法,所以在同一個類中的方法調用,添加@Async註解是失效的!,原因是當你在同一個類中的時候,方法調用是在類體內執行的,spring無法截獲這個方法調用(爲什麼呢,這個就是下文講的...奸笑...嘻嘻嘻嘻...)。
-
那在深入一步,Spring爲我們提供了AOP,面向切面的功能。他的原理和異步註解的原理是類似的,spring在啓動容器的時候,會掃描切面所定義的 類。在這些類被注入的時候,所注入的也是代理類,當你調用這些方法的時候,本質上是調用的代理類。通過代理類再去執行父類相對應的方法,那spring只需要在調用之前和之後執行某段代碼就完成了AOP的實現了!
SpringBoot環境中,要使用@Async註解,我們需要先在啓動類上加上@EnableAsync註解。這個與在SpringBoot中使用@Scheduled註解需要在啓動類中加上@EnableScheduling是一樣的道理(當然你使用古老的XML配置也是可以的,但是在SpringBoot環境中,建議的是全註解開發),具體原理下面會分析。加上@EnableAsync註解後,如果我們想在調用一個方法的時候開啓一個新的線程開始異步操作,我們只需要在這個方法上加上@Async註解,當然前提是,這個方法所在的類必須在Spring環境中。示例:非spingboot項目 <task:annotation-driven executor="annotationExecutor" /> <!-- 支持 @Async 註解 --> <task:executor id="annotationExecutor" pool-size="20"/>
執行流程:br/>掃描是否開啓註解EnableAsync,@EnableAsync註解上有個@Import(AsyncConfigurationSelector.class),springboot的注入老套路了
請您再移步AsyncConfigurationSelector,看到selectImports方法了沒,這裏使用的是默認使用的是ProxyAsyncConfiguration這個配置類
繼續觀摩ProxyAsyncConfiguration繼承AbstractAsyncConfiguration,它裏面的的setConfigurers說明了我們可以通過實現AsyncConfigurer接口來完成線程池以及異常處理器的配置,而且在Spring環境中只能配置一個實現類,否則會拋出異常。 上一點代碼:/** * Collect any {@link AsyncConfigurer} beans through autowiring. */ @Autowired(required = false) void setConfigurers(Collection<AsyncConfigurer> configurers) { if (CollectionUtils.isEmpty(configurers)) { return; } //AsyncConfigurer用來配置線程池配置以及異常處理器,而且在Spring環境中最多隻能有一個,在這裏我們知道了,如果想要自己去配置線程池,只需要實現AsyncConfigurer接口,並且不可以在Spring環境中有多個實現AsyncConfigurer的類。 if (configurers.size() > 1) { throw new IllegalStateException("Only one AsyncConfigurer may exist"); } AsyncConfigurer configurer = configurers.iterator().next(); this.executor = configurer.getAsyncExecutor(); this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler(); }
ProxyAsyncConfiguration注入的bean AsyncAnnotationBeanPostProcessor,這個BeanPostBeanPostProcessor很顯然會對帶有能夠引發異步操作的註解(比如@Async)的Bean進行處理
我們注意到AsyncAnnotationBeanPostProcessor有重寫父類的setBeanFactory,這個方法是不是有點熟悉呢,它是BeanFactoryAware接口中的方法,AsyncAnnotationBeanPostProcessor的父類實現了這個接口,在我們很久之前分析過的Bean的初始化中,是有提到過這個接口的,實現了Aware類型接口的Bean,會在初始化Bean的時候調用相應的初始化方法,具體可以查看AbstractAutowireCapableBeanFactory#initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)方法
處理Bean的postProcessAfterInitialization方法在祖先類AbstractAdvisingBeanPostProcessor中。從源碼中可以看到。AsyncAnnotationBeanPostProcessor是對Bean進行後置處理的BeanPostProcessor
最後代理到JdkDynamicAopProxy的invoke方法中,是用了責任鏈模式:List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);,將代理進行攔截來執行,通知鏈會包含setBeanFactory()方法生成的通知,執行鏈會用於創建ReflectiveMethodInvocation對象,最終是調用ReflectiveMethodInvocation的proceed()來完成對方法的增強處理,proceed()方法在這裏會執行最後一個分支
br/>具體執行的是AsyncExecutionInterceptor的invoke()
注意:雖然上文Spring環境中只能有一個AsyncConfigurer實現類,但是不意味着,在Spring環境中只能配置一個線程池,在Spring環境中是可以配置多個線程池,而且我們可以在使用@Async註解進行異步操作的時候,通過在value屬性上指定線程池BeanName,這樣就可以指定相應的線程池來作爲任務的載體,參見:determineAsyncExecutor
小結兄弟:
當我們想要在SpringBoot中方便的使用@Async註解開啓異步操作的時候,只需要實現AsyncConfigurer接口(這樣就配置了默認線程池配置,當然該類需要在Spring環境中,因爲是默認的,所以只能有一個,沒有多個實現類排優先級的說法),實現對線程池的配置,並在啓動類上加上@EnableAsync註解,即可使得@Async註解生效。br/>我們甚至可以不顯式的實現AsyncConfigurer,我們可以在Spring環境中配置多個Executor類型的Bean,在使用@Async註解時,將註解的value指定爲你Executor類型的BeanName,就可以使用指定的線程池來作爲任務的載體,這樣就使用線程池也更加靈活。
參考資料
[1]
你的@Async就真的異步嗎 ☞ 異步歷險奇遇記:
https://juejin.im/post/5d47a80a6fb9a06ad3470f9a
[2]
JAVA併發異步編程 原來十個接口的活現在只需要一個接口就搞定!: https://juejin.im/post/5d3c46d2f265da1b9163dbce
———— e n d ————
微服務、高併發、JVM調優、面試專欄等20大進階架構師專題請關注公衆號【Java進階架構師】後在菜單欄查看。
看到這裏,說明你喜歡本文
你的轉發,是對我最大的鼓勵!在看亦是支持↓