SpringMVC基于Servlet異步支持

本文是學(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

接下來看一下項目目錄

p10.png

DeferredResult

官方文檔

p1.png

官方文檔的意思是 要實現(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測試一下。

p2.png
p3.png

我們從返回結(jié)果和控制臺打印就可以看到確實實現(xiàn)了異步處理。

Callable

官方文檔

p4.png

通過文檔我們了解到使用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請求測試葛躏。

p5png.png
p6.png

我們從返回結(jié)果和控制臺打印看出了Callable異步處理也是可行的悔醋。

CompletionStage/CompletableFuture

官方文檔

p7.png

我們可以看到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請求測試蒲牧。

p8.png
P9.png

我們看到了線程切換的狀態(tài),證明異步也實現(xiàn)了翠订。

總結(jié)

SpringMVC異步處理大部分已經(jīng)介紹完了蕴轨,其實SpringMvc支持的異步已經(jīng)能夠滿足我們的基本開發(fā)需要橙弱,那么為什么Spring還要引入 WebFlux 技術(shù)棧斜筐,用小馬哥課中提到的就是 一種趨勢顷链,并發(fā)編程模型已經(jīng)成趨勢嗤练。之后會有單獨介紹這方面的內(nèi)容。

DEMO地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市残拐,隨后出現(xiàn)的幾起案子溪食,更是在濱河造成了極大的恐慌眠菇,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辐益,居然都是意外死亡智政,警方通過查閱死者的電腦和手機续捂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門劫拗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胁附,你說我怎么就攤上這事欲逃。” “怎么了弓叛?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長畦徘。 經(jīng)常有香客問我井辆,道長蒸播,這世上最難降的妖魔是什么袍榆? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任宿崭,我火速辦了婚禮,結(jié)果婚禮上琅摩,老公的妹妹穿的比我還像新娘蜕劝。我一直安慰自己岖沛,他們只是感情好婴削,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般衡楞。 火紅的嫁衣襯著肌膚如雪瘾境。 梳的紋絲不亂的頭發(fā)上迷守,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音沮协,去河邊找鬼慷暂。 笑死行瑞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氧吐。 我是一名探鬼主播筑舅,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了益缎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎励饵,沒想到半個月后滑燃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡典予,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衣摩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捂敌。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖占婉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酌予,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布霎终,位于F島的核電站莱褒,受9級特大地震影響涎劈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛛枚,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一蹦浦、第九天 我趴在偏房一處隱蔽的房頂上張望扭吁。 院中可真熱鬧盲镶,春花似錦、人聲如沸溉贿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甥捺,卻和暖如春裳擎,著一層夾襖步出監(jiān)牢的瞬間涎永,已是汗流浹背鹿响。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工惶我, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妈倔,地道東北人绸贡。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像捧挺,于是被迫代替她去往敵國和親尿瞭。 傳聞我的和親對象是個殘疾皇子闽烙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容