Springboot配置@Async無效的解決方案
Springboot配置@Async無效
Springboot中為了同時執(zhí)行多個任務(wù),或者同時查詢,加快查詢則使用多線程查詢,在springboot中,可以直接使用 @Async
注解來實現(xiàn)異步任務(wù),可以參考Springboot使用@Async實現(xiàn)異步任務(wù)簡單快捷使用,但是異步任務(wù)在某些情況下居然無效。
配置正確,代碼不報錯,但是無法使用多線程異步任務(wù),可能是下面的原因?qū)е拢?/strong>
1.沒有在springboot啟動類當中添加注解 @EnableAsync
注解。
2.異步方法使用注解@Async的返回值只能為void或者Future。
3.沒有走Spring的代理類。
因為@Transactional和@Async注解的實現(xiàn)都是基于Spring的AOP,而AOP的實現(xiàn)是基于動態(tài)代理實現(xiàn)的。
那么注解失效的原因就很明顯了,有可能因為調(diào)用方法的是對象本身而不是代理對象,沒有經(jīng)過Spring容器,無法使用代理對象調(diào)用。
4.注解的方法必須是 public
方法。編譯時非public方法會報錯
5.如果需要從 類的內(nèi)部 調(diào)用,需要先獲取其代理類
先注入ApplicationContext
@Autowired private ApplicationContext applicationContext;
然后在方法中通過ApplicationContext對象獲取當前類對象
applicationContext.getBean(Class);
6.調(diào)用的是靜態(tài)(static )方法,也會導致注解失效,因為注解是基于代理對象調(diào)用,而static方法是屬于類的,無法通過spring的代理對象直接調(diào)用
Springboot @Async失效的坑
異步應(yīng)用場景
為了提高接口的響應(yīng)性能,當業(yè)務(wù)非常復雜的情況下,可以將一部分跟業(yè)務(wù)關(guān)聯(lián)性不是特別強的邏輯進行異步處理。如日志記錄、短信發(fā)送、增加積分等。
通常而言會將此類業(yè)務(wù)邏輯通過異步的方式進行處理,從而加快接口的響應(yīng)速度,常用的解決方案有:
- 使用JDK 自定義線程池 讓代碼異步執(zhí)行
- 在springboot 中 使用@Async注解進行異步處理
- 使用中間件如mq 消息通知讓下游異步消費 如RocketMQ、KAFKA
使用第一種方式,需要精通線程池運行原理,結(jié)合實際的業(yè)務(wù)場景對隊列大小進行合理的設(shè)置。隊列設(shè)置過大過小都會存在內(nèi)存溢出的風險。
第三種方式是最合理的方式,它能夠通過MQ進行削峰填谷,通過合理的參數(shù)配置,保證數(shù)據(jù)不會丟失。但是架構(gòu)改動過大,對小型的單體應(yīng)用來講,工作量過大,成本過高。
在springboot 大行其道的情況下,考慮開發(fā)成本,以及項目時間關(guān)系選用第二種方式來解決代碼異步執(zhí)行的問題。
真實業(yè)務(wù)場景
線上問題
一個工單的分頁列表,前端控制了每個列表最大的顯示條數(shù)為100條。在業(yè)務(wù)流程中存在工單轉(zhuǎn)移的操作,轉(zhuǎn)移一筆工單至少包含以下幾個重要的步驟:
- 新增工單處理日志,如什么時間點將工單轉(zhuǎn)移給某人
- 修改工單當前處理人
- 發(fā)送企業(yè)微信給B端的跟進人(轉(zhuǎn)移人)
- 發(fā)送im信息給C端的用戶
由于公司采用微服務(wù)架構(gòu),因此每個業(yè)務(wù)模塊拆分的很細,在上述步驟中需要從其他系統(tǒng)中通過rpc調(diào)用接口拿到需要的數(shù)據(jù)才能完成整個業(yè)務(wù)流程數(shù)據(jù)的拼裝,如需要從crm系統(tǒng)拿到組織架構(gòu)信息,獲取轉(zhuǎn)移人的組織架構(gòu)、需要從udb用戶中心獲取轉(zhuǎn)移人的企業(yè)微信昵稱等。
因此在批量轉(zhuǎn)移的時候,前端會出現(xiàn)調(diào)用超時的問題,原因是dubbo接口默認的超時時間是15秒,由于業(yè)務(wù)復雜,導致在15秒內(nèi)執(zhí)行不完業(yè)務(wù)邏輯。
解決方案
- 將sql處理改為批量執(zhí)行,如新增處理日志 (batch insert);修改當前工單,使用case when 的方式一次性修改完成(批量update)
- 將發(fā)送消息改成異步處理 加快前端接口的響應(yīng)速度
- 讓接口提供方提供批量查詢的接口,避免rpc 循環(huán)調(diào)用在網(wǎng)絡(luò)上的消耗
優(yōu)化完成之后,接口的響應(yīng)速度由15秒多,變成了1秒。但是過程中遇到坑了。特此記錄一下
技術(shù)實現(xiàn)
優(yōu)化過程
@Async 注解定義為可以放置在方法上和類上,當使用在類上表明類所有的方法都能異步執(zhí)行。
在Springboot中是需要在方法上加上該注解就可以完美的實現(xiàn)異步執(zhí)行。
原始方法偽代碼如下
/** * 原始代碼 采用流水式的代碼一步步實現(xiàn) 業(yè)務(wù)邏輯 */ public void doBusiness(Object args){ //1. 新增工單處理日志,如什么時間點將工單轉(zhuǎn)移給某人 //2. 修改工單當前處理人 //3. 發(fā)送企業(yè)微信給B端的跟進人(轉(zhuǎn)移人) //4. 發(fā)送im信息給C端的用戶 }
那么異步問題就很好處理了,只需要將方法抽離形成多個子方法, 每個方法執(zhí)行自己的業(yè)務(wù)處理邏輯,然后再方法加上@Async注解不就ok了么,偽代碼如下
public void doBusiness(Object args){ //2. 修改工單當前處理人 this.doAsyncBusiness(); } // 單獨抽離一個異步執(zhí)行的方法 加上@Async注解 @Async private void doAsyncBusiness(Object args){ //1. 新增工單處理日志,如什么時間點將工單轉(zhuǎn)移給某人 //3. 發(fā)送企業(yè)微信給B端的跟進人(轉(zhuǎn)移人) //4. 發(fā)送im信息給C端的用戶 }
打完收工,重啟應(yīng)用,進行測試,然而并沒有像預(yù)期中的那樣,接口的響應(yīng)速度還是15秒左右。接著排查原因,可以肯定的是@Async是可以提供異步方法執(zhí)行。應(yīng)該是我們使用方式不對導致。
@Async 限制
熟悉Springboot AOP的同學可能會發(fā)現(xiàn)更改后的代碼存在明顯的問題
- 首先AOP代理機制要求 被代理的方法必須是 public , private 方法不能被代理
- 其次AOP代理機制會生成一個代理類 執(zhí)行代理方法 注意 this.doAsyncBusiness() 調(diào)用的是本對象的方法 ;
- 在啟動類上加上@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é)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring @schedule注解如何動態(tài)配置時間間隔
這篇文章主要介紹了spring @schedule注解如何動態(tài)配置時間間隔,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Java爬蟲Jsoup+httpclient獲取動態(tài)生成的數(shù)據(jù)
這篇文章主要介紹了Java爬蟲Jsoup+httpclient獲取動態(tài)生成的數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2017-05-05Java super關(guān)鍵字調(diào)用父類過程解析
這篇文章主要介紹了Java super關(guān)鍵字調(diào)用父類過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-1230分鐘入門Java8之默認方法和靜態(tài)接口方法學習
這篇文章主要介紹了30分鐘入門Java8之默認方法和靜態(tài)接口方法學習,詳細介紹了默認方法和接口,有興趣的可以了解一下。2017-04-04Java?Float?保留小數(shù)位精度的實現(xiàn)
這篇文章主要介紹了Java?Float?保留小數(shù)位精度的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java web攔截器inteceptor原理及應(yīng)用詳解
這篇文章主要介紹了java web攔截器inteceptor原理及應(yīng)用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01詳解Spring boot/Spring 統(tǒng)一錯誤處理方案的使用
這篇文章主要介紹了詳解Spring boot/Spring 統(tǒng)一錯誤處理方案的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06