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