Spring Boot 中的異步請求和異步調(diào)用詳解
在現(xiàn)代應(yīng)用程序中,異步處理可以顯著提升性能和響應(yīng)速度。除了異步請求,一般上我們用的比較多的應(yīng)該是異步調(diào)用。通常在開發(fā)過程中,會遇到一個方法是和實(shí)際業(yè)務(wù)無關(guān)的,沒有緊密性的。比如記錄日志信息等業(yè)務(wù)。這個時候正常就是啟一個新線程去做一些業(yè)務(wù)處理,讓主線程異步的執(zhí)行其他業(yè)務(wù)。本文將詳細(xì)介紹如何在 Spring Boot 中實(shí)現(xiàn)異步請求和異步調(diào)用,幫助大家在實(shí)際項(xiàng)目中應(yīng)用這些技術(shù)。
一、異步請求
1.1 使用 @Async 注解
Spring 提供了 @Async注解,使得我們可以簡單地將一個方法變成異步方法。為了啟用異步支持,需要在配置類上添加 @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); // 模擬長時間任務(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 "請求已提交";
}
}
1.2 使用 CompletableFuture
CompletableFuture 是 Java 8 引入的一個強(qiáng)大的異步工具類,Spring 也對其提供了良好的支持。
示例:
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); // 模擬長時間任務(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)用在請求中,也可以在方法之間的調(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); // 模擬長時間任務(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ù)已提交";
}
}
三、常見問題和注意事項(xiàng)
1.異步和同步請求的主要區(qū)別
兩者的使用場景不同,異步請求用來解決并發(fā)請求對服務(wù)器造成的壓力,從而提高對請求的吞吐量;而異步調(diào)用是用來做一些非主線流程且不需要實(shí)時計(jì)算和響應(yīng)的任務(wù),比如同步日志到kafka中做日志分析等。
異步請求是會一直等待response相應(yīng)的,需要返回結(jié)果給客戶端的;而異步調(diào)用我們往往會馬上返回給客戶端響應(yīng),完成這次整個的請求,至于異步調(diào)用的任務(wù)后臺自己慢慢跑就行,客戶端不會關(guān)心。
2.使用方式(基于spring下)
- 需要在啟動類加入@EnableAsync使異步調(diào)用@Async注解生效
- 在需要異步執(zhí)行的方法上加入此注解即可@Async("threadPool"),threadPool為自定義線程池
- 代碼略。。。就倆標(biāo)簽,自己試一把就可以了
3.注意事項(xiàng)
- 在默認(rèn)情況下,未設(shè)置TaskExecutor時,默認(rèn)是使用SimpleAsyncTaskExecutor這個線程池,但此線程不是真正意義上的線程池,因?yàn)榫€程不重用,每次調(diào)用都會創(chuàng)建一個新的線程??赏ㄟ^控制臺日志輸出可以看出,每次輸出線程名都是遞增的。所以最好我們來自定義一個線程池。
- 調(diào)用的異步方法,不能為同一個類的方法(包括同一個類的內(nèi)部類),簡單來說,因?yàn)镾pring在啟動掃描時會為其創(chuàng)建一個代理類,而同類調(diào)用時,還是調(diào)用本身的代理類的,所以和平常調(diào)用是一樣的。其他的注解如@Cache等也是一樣的道理,說白了,就是Spring的代理機(jī)制造成的。所以在開發(fā)中,最好把異步服務(wù)單獨(dú)抽出一個類來管理。下面會重點(diǎn)講述。。
4.什么情況下會導(dǎo)致@Async異步方法會失效?
- 調(diào)用同一個類下注有@Async異步方法:在spring中像@Async和@Transactional、cache等注解本質(zhì)使用的是動態(tài)代理,其實(shí)Spring容器在初始化的時候Spring容器會將含有AOP注解的類對象“替換”為代理對象(簡單這么理解),那么注解失效的原因就很明顯了,就是因?yàn)檎{(diào)用方法的是對象本身而不是代理對象,因?yàn)闆]有經(jīng)過Spring容器,那么解決方法也會沿著這個思路來解決。
- 調(diào)用的是靜態(tài)(static )方法
- 調(diào)用(private)私有化方法
5.解決4中問題a的方式(其它b,c兩個問題自己注意下就可以了)
- 將要異步執(zhí)行的方法單獨(dú)抽取成一個類,原理就是當(dāng)你把執(zhí)行異步的方法單獨(dú)抽取成一個類的時候,這個類肯定是被Spring管理的,其他Spring組件需要調(diào)用的時候肯定會注入進(jìn)去,這時候?qū)嶋H上注入進(jìn)去的就是代理類了。
- 其實(shí)我們的注入對象都是從Spring容器中給當(dāng)前Spring組件進(jìn)行成員變量的賦值,由于某些類使用了AOP注解,那么實(shí)際上在Spring容器中實(shí)際存在的是它的代理對象。那么我們就可以通過上下文獲取自己的代理對象調(diào)用異步方法。
四、總結(jié)
在 Spring Boot 中實(shí)現(xiàn)異步請求和異步調(diào)用可以顯著提升應(yīng)用程序的性能和響應(yīng)速度。通過使用 @Async注解和 CompletableFuture,我們可以輕松地將同步方法轉(zhuǎn)換為異步方法。本文介紹了具體的實(shí)現(xiàn)步驟和示例,希望能幫助你在項(xiàng)目中更好地應(yīng)用異步處理技術(shù)。