Spring Boot 中的異步請(qǐng)求和異步調(diào)用詳解
在現(xiàn)代應(yīng)用程序中,異步處理可以顯著提升性能和響應(yīng)速度。除了異步請(qǐng)求,一般上我們用的比較多的應(yīng)該是異步調(diào)用。通常在開發(fā)過(guò)程中,會(huì)遇到一個(gè)方法是和實(shí)際業(yè)務(wù)無(wú)關(guān)的,沒有緊密性的。比如記錄日志信息等業(yè)務(wù)。這個(gè)時(shí)候正常就是啟一個(gè)新線程去做一些業(yè)務(wù)處理,讓主線程異步的執(zhí)行其他業(yè)務(wù)。本文將詳細(xì)介紹如何在 Spring Boot 中實(shí)現(xiàn)異步請(qǐng)求和異步調(diào)用,幫助大家在實(shí)際項(xiàng)目中應(yīng)用這些技術(shù)。

一、異步請(qǐng)求
1.1 使用 @Async 注解
Spring 提供了 @Async注解,使得我們可以簡(jiǎn)單地將一個(gè)方法變成異步方法。為了啟用異步支持,需要在配置類上添加 @EnableAsync注解。
步驟:
1. 添加依賴:
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>2. 啟用異步支持:
import org.springframework.context.annotation.Configuration;
   import org.springframework.scheduling.annotation.EnableAsync;
   @Configuration
   @EnableAsync
   public class AsyncConfig {
   }3. 創(chuàng)建異步方法:
import org.springframework.scheduling.annotation.Async;
   import org.springframework.stereotype.Service;
   @Service
   public class AsyncService {
       @Async
       public void asyncMethod() {
           System.out.println("異步方法開始: " + Thread.currentThread().getName());
           try {
               Thread.sleep(2000); // 模擬長(zhǎng)時(shí)間任務(wù)
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("異步方法結(jié)束: " + Thread.currentThread().getName());
       }
   }4. 調(diào)用異步方法:
import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.web.bind.annotation.GetMapping;
   import org.springframework.web.bind.annotation.RestController;
   @RestController
   public class AsyncController {
       @Autowired
       private AsyncService asyncService;
       @GetMapping("/async")
       public String callAsync() {
           asyncService.asyncMethod();
           return "請(qǐng)求已提交";
       }
   }1.2 使用 CompletableFuture
CompletableFuture 是 Java 8 引入的一個(gè)強(qiáng)大的異步工具類,Spring 也對(duì)其提供了良好的支持。
示例:
1. 創(chuàng)建返回 CompletableFuture 的異步方法:
import org.springframework.scheduling.annotation.Async;
   import org.springframework.stereotype.Service;
   import java.util.concurrent.CompletableFuture;
   @Service
   public class AsyncService {
       @Async
       public CompletableFuture<String> asyncMethodWithResult() {
           System.out.println("異步方法開始: " + Thread.currentThread().getName());
           try {
               Thread.sleep(2000); // 模擬長(zhǎng)時(shí)間任務(wù)
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("異步方法結(jié)束: " + Thread.currentThread().getName());
           return CompletableFuture.completedFuture("異步任務(wù)完成");
       }
   }2. 調(diào)用異步方法并獲取結(jié)果:
import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.web.bind.annotation.GetMapping;
   import org.springframework.web.bind.annotation.RestController;
   import java.util.concurrent.CompletableFuture;
   import java.util.concurrent.ExecutionException;
   @RestController
   public class AsyncController {
       @Autowired
       private AsyncService asyncService;
       @GetMapping("/asyncResult")
       public String callAsyncWithResult() throws ExecutionException, InterruptedException {
           CompletableFuture<String> result = asyncService.asyncMethodWithResult();
           return result.get();
       }
   }二、異步調(diào)用
異步調(diào)用不僅可以應(yīng)用在請(qǐng)求中,也可以在方法之間的調(diào)用中使用。
2.1 使用 @Async 進(jìn)行方法調(diào)用
可以直接在類的方法上使用 @Async 注解,使其成為異步方法。
示例:
1. 創(chuàng)建異步調(diào)用方法:
import org.springframework.scheduling.annotation.Async;
   import org.springframework.stereotype.Component;
   @Component
   public class AsyncComponent {
       @Async
       public void asyncMethod() {
           System.out.println("異步方法開始: " + Thread.currentThread().getName());
           try {
               Thread.sleep(2000); // 模擬長(zhǎng)時(shí)間任務(wù)
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("異步方法結(jié)束: " + Thread.currentThread().getName());
       }
   }2. 在其他類中調(diào)用異步方法:
import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.stereotype.Service;
   @Service
   public class SomeService {
       @Autowired
       private AsyncComponent asyncComponent;
       public void performAsyncTask() {
           System.out.println("主線程開始: " + Thread.currentThread().getName());
           asyncComponent.asyncMethod();
           System.out.println("主線程結(jié)束: " + Thread.currentThread().getName());
       }
   }3. 在控制器中調(diào)用服務(wù)方法:
import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.web.bind.annotation.GetMapping;
   import org.springframework.web.bind.annotation.RestController;
   @RestController
   public class SomeController {
       @Autowired
       private SomeService someService;
       @GetMapping("/performTask")
       public String performTask() {
           someService.performAsyncTask();
           return "任務(wù)已提交";
       }
   }三、常見問(wèn)題和注意事項(xiàng)
1.異步和同步請(qǐng)求的主要區(qū)別
兩者的使用場(chǎng)景不同,異步請(qǐng)求用來(lái)解決并發(fā)請(qǐng)求對(duì)服務(wù)器造成的壓力,從而提高對(duì)請(qǐng)求的吞吐量;而異步調(diào)用是用來(lái)做一些非主線流程且不需要實(shí)時(shí)計(jì)算和響應(yīng)的任務(wù),比如同步日志到kafka中做日志分析等。
異步請(qǐng)求是會(huì)一直等待response相應(yīng)的,需要返回結(jié)果給客戶端的;而異步調(diào)用我們往往會(huì)馬上返回給客戶端響應(yīng),完成這次整個(gè)的請(qǐng)求,至于異步調(diào)用的任務(wù)后臺(tái)自己慢慢跑就行,客戶端不會(huì)關(guān)心。
2.使用方式(基于spring下)
- 需要在啟動(dòng)類加入@EnableAsync使異步調(diào)用@Async注解生效
 - 在需要異步執(zhí)行的方法上加入此注解即可@Async("threadPool"),threadPool為自定義線程池
 - 代碼略。。。就倆標(biāo)簽,自己試一把就可以了
 
3.注意事項(xiàng)
- 在默認(rèn)情況下,未設(shè)置TaskExecutor時(shí),默認(rèn)是使用SimpleAsyncTaskExecutor這個(gè)線程池,但此線程不是真正意義上的線程池,因?yàn)榫€程不重用,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程??赏ㄟ^(guò)控制臺(tái)日志輸出可以看出,每次輸出線程名都是遞增的。所以最好我們來(lái)自定義一個(gè)線程池。
 - 調(diào)用的異步方法,不能為同一個(gè)類的方法(包括同一個(gè)類的內(nèi)部類),簡(jiǎn)單來(lái)說(shuō),因?yàn)镾pring在啟動(dòng)掃描時(shí)會(huì)為其創(chuàng)建一個(gè)代理類,而同類調(diào)用時(shí),還是調(diào)用本身的代理類的,所以和平常調(diào)用是一樣的。其他的注解如@Cache等也是一樣的道理,說(shuō)白了,就是Spring的代理機(jī)制造成的。所以在開發(fā)中,最好把異步服務(wù)單獨(dú)抽出一個(gè)類來(lái)管理。下面會(huì)重點(diǎn)講述。。
 
4.什么情況下會(huì)導(dǎo)致@Async異步方法會(huì)失效?
- 調(diào)用同一個(gè)類下注有@Async異步方法:在spring中像@Async和@Transactional、cache等注解本質(zhì)使用的是動(dòng)態(tài)代理,其實(shí)Spring容器在初始化的時(shí)候Spring容器會(huì)將含有AOP注解的類對(duì)象“替換”為代理對(duì)象(簡(jiǎn)單這么理解),那么注解失效的原因就很明顯了,就是因?yàn)檎{(diào)用方法的是對(duì)象本身而不是代理對(duì)象,因?yàn)闆]有經(jīng)過(guò)Spring容器,那么解決方法也會(huì)沿著這個(gè)思路來(lái)解決。
 - 調(diào)用的是靜態(tài)(static )方法
 - 調(diào)用(private)私有化方法
 
5.解決4中問(wèn)題a的方式(其它b,c兩個(gè)問(wèn)題自己注意下就可以了)
- 將要異步執(zhí)行的方法單獨(dú)抽取成一個(gè)類,原理就是當(dāng)你把執(zhí)行異步的方法單獨(dú)抽取成一個(gè)類的時(shí)候,這個(gè)類肯定是被Spring管理的,其他Spring組件需要調(diào)用的時(shí)候肯定會(huì)注入進(jìn)去,這時(shí)候?qū)嶋H上注入進(jìn)去的就是代理類了。
 - 其實(shí)我們的注入對(duì)象都是從Spring容器中給當(dāng)前Spring組件進(jìn)行成員變量的賦值,由于某些類使用了AOP注解,那么實(shí)際上在Spring容器中實(shí)際存在的是它的代理對(duì)象。那么我們就可以通過(guò)上下文獲取自己的代理對(duì)象調(diào)用異步方法。
 
四、總結(jié)
在 Spring Boot 中實(shí)現(xiàn)異步請(qǐng)求和異步調(diào)用可以顯著提升應(yīng)用程序的性能和響應(yīng)速度。通過(guò)使用 @Async注解和 CompletableFuture,我們可以輕松地將同步方法轉(zhuǎn)換為異步方法。本文介紹了具體的實(shí)現(xiàn)步驟和示例,希望能幫助你在項(xiàng)目中更好地應(yīng)用異步處理技術(shù)。















 
 
 





 
 
 
 