本文是學(xué)習(xí)了小馬哥在慕課網(wǎng)的課程的《Spring Boot 2.0深度實踐之核心技術(shù)篇》的內(nèi)容結(jié)合自己的需要和理解做的筆記。
大家都知道在springboot2.0版本之后推出了 基于Reactive Programming編程模型的WebFlux技術(shù)棧與SpringMvc 并存涝焙,稍后會有單獨介紹WebFlux的相關(guān)內(nèi)容纱皆,在我們看來WebFlux技術(shù)棧最簡單直接的解釋就是異步非阻塞搀缠。但是我們也知道在Servlet 3.0 之后 也支持異步非阻塞請求艺普,而SpringMvc就是在Servlet引擎基礎(chǔ)上創(chuàng)建的岸浑。
概要
那么我們就簡單介紹一下在SpringMvc技術(shù)棧下的相關(guān)的3種異步請求矢洲。
- DeferredResult
- Callable
- CompletionStage/CompletableFuture
接下來看一下項目目錄
DeferredResult
官方文檔
官方文檔的意思是 要實現(xiàn)DeferredResult
控制器可以從不同的線程異步生成返回值 - 例如盖桥,消息隊列揩徊,計劃任務(wù)或其他事件塑荒。
那么我就按照文檔所說,來實現(xiàn)一個異步請求吧乌助。
具體實現(xiàn)
我們就使用一個監(jiān)聽器來模擬消息隊列,來看看是否如文檔所說他托,實現(xiàn)異步操作。在定義監(jiān)聽器之前把篓,我們還需要一個阻塞隊列韧掩。
阻塞隊列--SimilarQueueHolder
/**
* 模擬消息隊列
*/
@Component
public class SimilarQueueHolder {
//創(chuàng)建容量為10的阻塞隊列
private BlockingQueue<DeferredResult<String>> blockingDeque = new ArrayBlockingQueue<DeferredResult<String>>(10);
public BlockingQueue<DeferredResult<String>> getBlockingDeque() {
return blockingDeque;
}
public void setBlockingDeque(BlockingQueue<DeferredResult<String>> blockingDeque) {
this.blockingDeque = blockingDeque;
}
}
我們可以看到就是一個簡單的Bean里聲明了一個容量為10的阻塞隊列。
監(jiān)聽器--QueueListener
/**
* 使用監(jiān)聽器來模擬消息隊列處理
*/
@Configuration
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private SimilarQueueHolder similarQueueHolder;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
new Thread(()->{
while(true) {
try {
//從隊列中取出DeferredResult
DeferredResult<String> deferredResult = similarQueueHolder.getBlockingDeque().take();
printlnThread("開始DeferredResult異步處理");
//模擬處理時間
TimeUnit.SECONDS.sleep(3);
printlnThread("結(jié)束DeferredResult異步處理");
//模擬處理完成賦值
deferredResult.setResult("Hello World from DeferredResult");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 打印當(dāng)前線程
* @param object
*/
private void printlnThread(Object object) {
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
}
}
Controller--DeferredResultHelloWorldController
@RestController
public class DeferredResultHelloWorldController {
@Autowired
private SimilarQueueHolder similarQueueHolder;
@GetMapping("/deferred/result")
public DeferredResult<String> deferredResultHelloWolrd() {
printlnThread("主線程--deferredResultHelloWolrd開始執(zhí)行");
//聲明異步DeferredResult
DeferredResult<String> deferredResult = new DeferredResult<>();
//模擬放入消息隊列
similarQueueHolder.getBlockingDeque().offer(deferredResult);
printlnThread("主線程--deferredResultHelloWolrd結(jié)束執(zhí)行");
return deferredResult;
}
/**
* 打印當(dāng)前線程
* @param object
*/
private void printlnThread(Object object) {
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
}
}
啟動類--SpringServletAsynBootStrap
@SpringBootApplication
public class SpringServletAsynBootStrap {
public static void main(String[] args) {
SpringApplication.run(SpringServletAsynBootStrap.class,args);
}
}
因為這個啟動類是通用的,在這我就給出一次聋庵。
啟動測試
我們啟動一下Springboot容器然后使用PostMan測試一下。
我們從返回結(jié)果和控制臺打印就可以看到確實實現(xiàn)了異步處理。
Callable
官方文檔
通過文檔我們了解到使用JDK中的java.util.concurrent.Callable
就可以通過配置的TaskExecutor
運行給定任務(wù)來獲取返回值择份。
具體實現(xiàn)
通過文檔解釋荣赶,我們這里需要先配置一個TaskExecutor
支持異步處理利诺,如果不配置,那么Springboot會啟用自身默認(rèn)的SimpleAsyncTaskExecutor
來處理異步。
配置類--AsynWebConfig 配置TaskExecutor
支持的異步處理
/**
* 異步配置類
*/
@Configuration
public class AsynWebConfig implements WebMvcConfigurer {
//配置自定義TaskExecutor
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(60 * 1000L);
configurer.registerCallableInterceptors(timeoutInterceptor());
configurer.setTaskExecutor(threadPoolTaskExecutor());
}
//異步處理攔截
@Bean
public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
return new TimeoutCallableProcessingInterceptor();
}
//異步線程池
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.setCorePoolSize(5);
t.setMaxPoolSize(10);
t.setThreadNamePrefix("NEAL");
return t;
}
}
Controller--CallableHelloWorldController
/**
* Callback Controller層
*/
@RestController
public class CallableHelloWorldController {
@GetMapping("/callable/hello")
public Callable<String> helloWorld() {
printlnThread("CallableHelloWorldController---主線程開始");
return new Callable<String>() {
public String call() throws Exception {
//模擬處理時間
printlnThread("異步處理開始---Callable");
TimeUnit.SECONDS.sleep(3);
printlnThread("異步處理結(jié)束---Callable");
return "Hello World from Callable";
}
};
}
/**
* 打印當(dāng)前線程
* @param object
*/
private void printlnThread(Object object) {
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
}
}
啟動測試
啟動容器胜卤,我們使用postMan請求測試葛躏。
我們從返回結(jié)果和控制臺打印看出了Callable
異步處理也是可行的悔醋。
CompletionStage/CompletableFuture
官方文檔
我們可以看到CompletableFuture /CompletionStage 是DeferredResult 的替代方案猾愿。
具體實現(xiàn)
Controller--CompletableAsynController
/**
* CompletionStage /CompletableFuture Controller層
*/
@RestController
public class CompletableAsynController {
@GetMapping("/completion-stage")
public CompletionStage<String> completionStage(){
printlnThread("OtherAsynController---主線程開始");
return CompletableFuture.supplyAsync(()->{
//模擬處理時間
printlnThread("異步處理開始---CompletableFuture");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
printlnThread("異步處理結(jié)束---CompletableFuture");
return "Hello World from OtherAsynController"; // 異步執(zhí)行結(jié)果
});
}
/**
* 打印當(dāng)前線程
* @param object
*/
private void printlnThread(Object object) {
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
}
}
啟動測試
啟動容器姻僧,我們使用postMan請求測試蒲牧。
我們看到了線程切換的狀態(tài),證明異步也實現(xiàn)了翠订。
總結(jié)
SpringMVC異步處理大部分已經(jīng)介紹完了蕴轨,其實SpringMvc支持的異步已經(jīng)能夠滿足我們的基本開發(fā)需要橙弱,那么為什么Spring還要引入 WebFlux 技術(shù)棧斜筐,用小馬哥課中提到的就是 一種趨勢顷链,并發(fā)編程模型已經(jīng)成趨勢嗤练。之后會有單獨介紹這方面的內(nèi)容。