Springboot配置@Async無效的解決方案
Springboot配置@Async無效
Springboot中為了同時(shí)執(zhí)行多個(gè)任務(wù),或者同時(shí)查詢,加快查詢則使用多線程查詢,在springboot中,可以直接使用 @Async注解來實(shí)現(xiàn)異步任務(wù),可以參考Springboot使用@Async實(shí)現(xiàn)異步任務(wù)簡(jiǎn)單快捷使用,但是異步任務(wù)在某些情況下居然無效。
配置正確,代碼不報(bào)錯(cuò),但是無法使用多線程異步任務(wù),可能是下面的原因?qū)е拢?/strong>
1.沒有在springboot啟動(dòng)類當(dāng)中添加注解 @EnableAsync注解。
2.異步方法使用注解@Async的返回值只能為void或者Future。
3.沒有走Spring的代理類。
因?yàn)锧Transactional和@Async注解的實(shí)現(xiàn)都是基于Spring的AOP,而AOP的實(shí)現(xiàn)是基于動(dòng)態(tài)代理實(shí)現(xiàn)的。
那么注解失效的原因就很明顯了,有可能因?yàn)檎{(diào)用方法的是對(duì)象本身而不是代理對(duì)象,沒有經(jīng)過Spring容器,無法使用代理對(duì)象調(diào)用。
4.注解的方法必須是 public 方法。編譯時(shí)非public方法會(huì)報(bào)錯(cuò)
5.如果需要從 類的內(nèi)部 調(diào)用,需要先獲取其代理類
先注入ApplicationContext
@Autowired private ApplicationContext applicationContext;
然后在方法中通過ApplicationContext對(duì)象獲取當(dāng)前類對(duì)象
applicationContext.getBean(Class);
6.調(diào)用的是靜態(tài)(static )方法,也會(huì)導(dǎo)致注解失效,因?yàn)樽⒔馐腔诖韺?duì)象調(diào)用,而static方法是屬于類的,無法通過spring的代理對(duì)象直接調(diào)用
Springboot @Async失效的坑
異步應(yīng)用場(chǎng)景
為了提高接口的響應(yīng)性能,當(dāng)業(yè)務(wù)非常復(fù)雜的情況下,可以將一部分跟業(yè)務(wù)關(guān)聯(lián)性不是特別強(qiáng)的邏輯進(jìn)行異步處理。如日志記錄、短信發(fā)送、增加積分等。
通常而言會(huì)將此類業(yè)務(wù)邏輯通過異步的方式進(jìn)行處理,從而加快接口的響應(yīng)速度,常用的解決方案有:
- 使用JDK 自定義線程池 讓代碼異步執(zhí)行
- 在springboot 中 使用@Async注解進(jìn)行異步處理
- 使用中間件如mq 消息通知讓下游異步消費(fèi) 如RocketMQ、KAFKA
使用第一種方式,需要精通線程池運(yùn)行原理,結(jié)合實(shí)際的業(yè)務(wù)場(chǎng)景對(duì)隊(duì)列大小進(jìn)行合理的設(shè)置。隊(duì)列設(shè)置過大過小都會(huì)存在內(nèi)存溢出的風(fēng)險(xiǎn)。
第三種方式是最合理的方式,它能夠通過MQ進(jìn)行削峰填谷,通過合理的參數(shù)配置,保證數(shù)據(jù)不會(huì)丟失。但是架構(gòu)改動(dòng)過大,對(duì)小型的單體應(yīng)用來講,工作量過大,成本過高。
在springboot 大行其道的情況下,考慮開發(fā)成本,以及項(xiàng)目時(shí)間關(guān)系選用第二種方式來解決代碼異步執(zhí)行的問題。
真實(shí)業(yè)務(wù)場(chǎng)景
線上問題
一個(gè)工單的分頁列表,前端控制了每個(gè)列表最大的顯示條數(shù)為100條。在業(yè)務(wù)流程中存在工單轉(zhuǎn)移的操作,轉(zhuǎn)移一筆工單至少包含以下幾個(gè)重要的步驟:
- 新增工單處理日志,如什么時(shí)間點(diǎn)將工單轉(zhuǎn)移給某人
- 修改工單當(dāng)前處理人
- 發(fā)送企業(yè)微信給B端的跟進(jìn)人(轉(zhuǎn)移人)
- 發(fā)送im信息給C端的用戶
由于公司采用微服務(wù)架構(gòu),因此每個(gè)業(yè)務(wù)模塊拆分的很細(xì),在上述步驟中需要從其他系統(tǒng)中通過rpc調(diào)用接口拿到需要的數(shù)據(jù)才能完成整個(gè)業(yè)務(wù)流程數(shù)據(jù)的拼裝,如需要從crm系統(tǒng)拿到組織架構(gòu)信息,獲取轉(zhuǎn)移人的組織架構(gòu)、需要從udb用戶中心獲取轉(zhuǎn)移人的企業(yè)微信昵稱等。
因此在批量轉(zhuǎn)移的時(shí)候,前端會(huì)出現(xiàn)調(diào)用超時(shí)的問題,原因是dubbo接口默認(rèn)的超時(shí)時(shí)間是15秒,由于業(yè)務(wù)復(fù)雜,導(dǎo)致在15秒內(nèi)執(zhí)行不完業(yè)務(wù)邏輯。
解決方案
- 將sql處理改為批量執(zhí)行,如新增處理日志 (batch insert);修改當(dāng)前工單,使用case when 的方式一次性修改完成(批量update)
- 將發(fā)送消息改成異步處理 加快前端接口的響應(yīng)速度
- 讓接口提供方提供批量查詢的接口,避免rpc 循環(huán)調(diào)用在網(wǎng)絡(luò)上的消耗
優(yōu)化完成之后,接口的響應(yīng)速度由15秒多,變成了1秒。但是過程中遇到坑了。特此記錄一下
技術(shù)實(shí)現(xiàn)
優(yōu)化過程

@Async 注解定義為可以放置在方法上和類上,當(dāng)使用在類上表明類所有的方法都能異步執(zhí)行。
在Springboot中是需要在方法上加上該注解就可以完美的實(shí)現(xiàn)異步執(zhí)行。
原始方法偽代碼如下
/**
* 原始代碼 采用流水式的代碼一步步實(shí)現(xiàn) 業(yè)務(wù)邏輯
*/
public void doBusiness(Object args){
//1. 新增工單處理日志,如什么時(shí)間點(diǎn)將工單轉(zhuǎn)移給某人
//2. 修改工單當(dāng)前處理人
//3. 發(fā)送企業(yè)微信給B端的跟進(jìn)人(轉(zhuǎn)移人)
//4. 發(fā)送im信息給C端的用戶
}那么異步問題就很好處理了,只需要將方法抽離形成多個(gè)子方法, 每個(gè)方法執(zhí)行自己的業(yè)務(wù)處理邏輯,然后再方法加上@Async注解不就ok了么,偽代碼如下
public void doBusiness(Object args){
//2. 修改工單當(dāng)前處理人
this.doAsyncBusiness();
}
// 單獨(dú)抽離一個(gè)異步執(zhí)行的方法 加上@Async注解
@Async
private void doAsyncBusiness(Object args){
//1. 新增工單處理日志,如什么時(shí)間點(diǎn)將工單轉(zhuǎn)移給某人
//3. 發(fā)送企業(yè)微信給B端的跟進(jìn)人(轉(zhuǎn)移人)
//4. 發(fā)送im信息給C端的用戶
}打完收工,重啟應(yīng)用,進(jìn)行測(cè)試,然而并沒有像預(yù)期中的那樣,接口的響應(yīng)速度還是15秒左右。接著排查原因,可以肯定的是@Async是可以提供異步方法執(zhí)行。應(yīng)該是我們使用方式不對(duì)導(dǎo)致。
@Async 限制
熟悉Springboot AOP的同學(xué)可能會(huì)發(fā)現(xiàn)更改后的代碼存在明顯的問題
- 首先AOP代理機(jī)制要求 被代理的方法必須是 public , private 方法不能被代理
- 其次AOP代理機(jī)制會(huì)生成一個(gè)代理類 執(zhí)行代理方法 注意 this.doAsyncBusiness() 調(diào)用的是本對(duì)象的方法 ;
- 在啟動(dòng)類上加上@EnableAsync注解
綜上所述,原因我們已經(jīng)通過AOP代理的原理找到了。下面摘自官方文檔的一段話:
- it must be applied to public methods only
- self-invocation – calling the async method from within the same class – won’t work
The reasons are simple – 「the method needs to be public」 so that it can be proxied. And 「self-invocation doesn’t work」 because it bypasses the proxy and calls the underlying method directly.
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
利用Intellij Idea連接遠(yuǎn)程服務(wù)器實(shí)現(xiàn)遠(yuǎn)程上傳部署功能
大家在使用Intellij Idea開發(fā)程序的時(shí)候,是不是需要部署到遠(yuǎn)程SSH服務(wù)器運(yùn)行呢,當(dāng)然也可以直接在idea軟件內(nèi)容實(shí)現(xiàn)配置部署操作,接下來通過本文給大家分享利用Intellij Idea連接遠(yuǎn)程服務(wù)器實(shí)現(xiàn)遠(yuǎn)程上傳部署功能,感興趣的朋友跟隨小編一起看看吧2021-05-05
RepeatSubmit若依框架如何防止表單重復(fù)提交注解
若依框架中的@RepeatSubmit注解用于防止表單重復(fù)提交,通過在控制器方法上添加該注解,并在前端頁面和JavaScript代碼中實(shí)現(xiàn)雙重校驗(yàn),可以確保同一用戶在短時(shí)間內(nèi)不會(huì)重復(fù)提交相同的表單2024-11-11
用java生成html文件實(shí)現(xiàn)原理及代碼
用printStream來向html文件里輸出數(shù)據(jù),先創(chuàng)建一個(gè)StringBuilder對(duì)象,通過append方法來為其添加html語句,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08
springboot springmvc拋出全局異常的解決方法
這篇文章主要為大家詳細(xì)介紹了springboot springmvc拋出全局異常的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)
Hibernate想要實(shí)現(xiàn)雙向的關(guān)聯(lián)就必須在映射文件的兩端同時(shí)配置<one-to-one>,另外還要在主映射的一端采用foreign外鍵關(guān)聯(lián)屬性,下面我們就一起來看一下Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)方法:2016-06-06

