自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Boot 實(shí)現(xiàn)方法異步調(diào)用的正確姿勢(shì)!

開發(fā) 前端
如果希望采用自定義線程池來執(zhí)行,可以配置一個(gè)線程池對(duì)象并注入到 bean 工廠,最后在異步注解中指定即可;也可以全局配置默認(rèn)線程池。

01、背景介紹

在實(shí)際的項(xiàng)目開發(fā)過程中,通常會(huì)碰到某個(gè)方法內(nèi)各個(gè)邏輯并非緊密相連的業(yè)務(wù)。比如查詢文章詳情后更新文章閱讀量,其實(shí)對(duì)于用戶來說,最關(guān)心的是能快速獲取文章,至于更新文章閱讀量,用戶可能并不關(guān)心。

因此,對(duì)于這類邏輯并非緊密相連的業(yè)務(wù),可以將邏輯進(jìn)行拆分,讓用戶無需等待更新文章閱讀量,查詢時(shí)直接返回文章信息,縮短同步請(qǐng)求的耗時(shí),進(jìn)一步提升了用戶體驗(yàn)。

要實(shí)現(xiàn)這種效果,很多同學(xué)可能立刻想到,采用異步線程來更新文章閱讀量。

是的,這個(gè)思路沒錯(cuò),在 Java 項(xiàng)目中,我們可以開啟一個(gè)線程來實(shí)現(xiàn)方法異步執(zhí)行。

如果是在 Spring Boot 工程中,該如何優(yōu)雅的實(shí)現(xiàn)方法異步調(diào)用呢?

今天帶著這個(gè)問題,我們一起來學(xué)習(xí)一下如何在 Spring Boot 中實(shí)現(xiàn)方法的異步調(diào)用。

02、方案實(shí)踐

實(shí)際上,從 Spring 3.0 之后,在 Spring Framework 的 Spring Task 模塊中,提供了@Async注解,將其添加在方法上,就可以自動(dòng)實(shí)現(xiàn)該方法的異步調(diào)用效果。

不過有一個(gè)前提,需要在啟動(dòng)類或配置類加上@EnableAsync注解,以便使異步調(diào)用@Async注解生效。

2.1、異步調(diào)用簡(jiǎn)單示例

以用戶查詢文章詳情后,異步更新文章閱讀量為例,我們來看一個(gè)簡(jiǎn)單的應(yīng)用示例。

2.1.1、service 層代碼
@Component
public class ArticleService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ArticleService.class);

    /**
     * 查詢文章信息
     * @return
     */
    public String queryArticle(){
        LOGGER.info("查詢文章信息...");
        return "hello world";
    }

    /**
     * 更新文章閱讀量
     * @return
     */
    @Async
    public void updateCount(){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.info("更新文章閱讀量...");
    }
}
2.1.2、controller 層代碼
@RestController
public class UserController {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private ArticleService articleService;

    @RequestMapping("/query")
    public String query(){
        LOGGER.info("用戶請(qǐng)求開始");
        // 查詢文章
        String result = articleService.queryArticle();
        // 更新文章閱讀量
        articleService.updateCount();
        LOGGER.info("用戶請(qǐng)求結(jié)束");
        return result;
    }
}
2.1.3、啟動(dòng)類或配置類添加 EnableAsync 注解
@EnableAsync
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
2.1.4、服務(wù)測(cè)試

最后啟動(dòng)服務(wù),在瀏覽器中向query接口方法發(fā)起請(qǐng)求,輸出結(jié)果如下:

圖片圖片

從日志上可以清晰的看到,當(dāng)發(fā)起查詢文章請(qǐng)求的時(shí)候,結(jié)果立刻響應(yīng)給了客戶端;其次,更新文章閱讀量的方法采用的是task-1線程來執(zhí)行,并沒有阻塞主線程的執(zhí)行,異步調(diào)用效果明顯。

2.2、自定義線程池執(zhí)行異步方法

被@Async注解標(biāo)注的方法,默認(rèn)采用SimpleAsyncTaskExecutor線程池來執(zhí)行。這個(gè)線程池有一個(gè)特點(diǎn)就是,每來一個(gè)請(qǐng)求任務(wù)就會(huì)創(chuàng)建一個(gè)線程去執(zhí)行,如果系統(tǒng)不斷的創(chuàng)建線程,最終可能導(dǎo)致 CPU 和內(nèi)存占用過高,引發(fā)OutOfMemoryError錯(cuò)誤。

實(shí)際上,SimpleAsyncTaskExecutor并不是嚴(yán)格意義上的線程池,因?yàn)樗_(dá)不到線程復(fù)用的效果。因此,在實(shí)際開發(fā)中,建議自定義線程池來執(zhí)行異步方法。

實(shí)現(xiàn)步驟也很簡(jiǎn)單,首先,注入自定義線程池對(duì)象到 Spring Bean 中;然后,在@Async注解中指定線程池,即可實(shí)現(xiàn)指定線程池來異步執(zhí)行任務(wù)。

2.2.1、配置自定義線程池類
@Configuration
public class AsyncConfig {

    @Bean("customExecutor")
    public ThreadPoolTaskExecutor asyncOperationExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 設(shè)置核心線程數(shù)
        executor.setCorePoolSize(3);
        // 設(shè)置最大線程數(shù)
        executor.setMaxPoolSize(5);
        // 設(shè)置隊(duì)列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 設(shè)置線程活躍時(shí)間(秒)
        executor.setKeepAliveSeconds(30);
        // 設(shè)置線程名前綴+分組名稱
        executor.setThreadNamePrefix("customThread-");
        executor.setThreadGroupName("customThreadGroup");
        // 所有任務(wù)結(jié)束后關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化
        executor.initialize();
        return executor;
    }
}
2.2.2、在方法注解上指定線程池

比如,將更新文章閱讀量的方法,改成customExecutor線程池來執(zhí)行,在@Async注解上指定線程池即可。

@Async("customExecutor")
public void updateCount(){
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    LOGGER.info("更新文章閱讀量...");
}
2.2.3、服務(wù)測(cè)試

最后啟動(dòng)服務(wù),重新發(fā)起請(qǐng)求,輸出結(jié)果如下:

圖片圖片

從日志上可以清晰的看到,更新方法采用了customThread-1線程來異步執(zhí)行任務(wù)。

2.3、配置全局默認(rèn)線程池

從上文中我們得知,被@Async注解標(biāo)注的方法,默認(rèn)采用SimpleAsyncTaskExecutor線程池來執(zhí)行。

某些場(chǎng)景下,如果希望系統(tǒng)統(tǒng)一采用自定義配置線程池來執(zhí)行任務(wù),但是又不想在被@Async注解的方法上一個(gè)一個(gè)的去指定線程池,如何處理呢?

此時(shí)可以重寫AsyncConfigurer接口的getAsyncExecutor()方法,配置默認(rèn)線程池。

實(shí)現(xiàn)也很簡(jiǎn)單,示例如下!

2.3.1、自定義默認(rèn)異步線程池
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 設(shè)置核心線程數(shù)
        executor.setCorePoolSize(3);
        // 設(shè)置最大線程數(shù)
        executor.setMaxPoolSize(5);
        // 設(shè)置隊(duì)列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 設(shè)置線程活躍時(shí)間(秒)
        executor.setKeepAliveSeconds(30);
        // 設(shè)置線程名前綴+分組名稱
        executor.setThreadNamePrefix("asyncThread-");
        executor.setThreadGroupName("asyncThreadGroup");
        // 所有任務(wù)結(jié)束后關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, obj) ->{
            System.out.println("異步調(diào)用,異常捕獲---------------------------------");
            System.out.println("Exception message - " + throwable.getMessage());
            System.out.println("Method name - " + method.getName());
            for (Object param : obj) {
                System.out.println("Parameter value - " + param);
            }
            System.out.println("異步調(diào)用,異常捕獲---------------------------------");
        };
    }
}
2.3.2、服務(wù)測(cè)試

將@Async注解中指定的線程池,最后啟動(dòng)服務(wù),重新發(fā)起請(qǐng)求,輸出結(jié)果如下:

從日志上可以清晰的看到,更新方法采用了asyncThread-1線程來異步執(zhí)行任務(wù)。

03、遇到的一些坑

在使用@Async注解的時(shí)候,可能會(huì)失效,總結(jié)下來主要有以下幾個(gè)場(chǎng)景。

  • 場(chǎng)景一:異步方法使用static修飾,此時(shí)不會(huì)生效
  • 場(chǎng)景二:調(diào)用的異步方法,在同一個(gè)類中,此時(shí)不會(huì)生效。因?yàn)?Spring 在啟動(dòng)掃描時(shí)會(huì)為其創(chuàng)建一個(gè)代理類,而同類調(diào)用時(shí),還是調(diào)用本身的代理類的,所以還是同步調(diào)用
  • 場(chǎng)景三:異步類沒有使用@Component、@Service等注解,導(dǎo)致 spring 無法掃描到異步類,此時(shí)不會(huì)生效
  • 場(chǎng)景四:采用SpringBoot框架開發(fā)時(shí),沒有在啟動(dòng)類上添加@EnableAsync注解,此時(shí)不會(huì)生效

其次,關(guān)于事務(wù)機(jī)制的一些問題,直接在@Async方法上再標(biāo)注@Transactional是會(huì)失效的,此時(shí)可以在方法內(nèi)采用編程式事務(wù)方式來提交數(shù)據(jù)。但是,在@Async方法調(diào)用其它類的方法上標(biāo)注的@Transactional注解有效。

04、小結(jié)

最后總結(jié)一下,在 Spring Boot 工程中,如果想要實(shí)現(xiàn)方法異步執(zhí)行的效果,只需要兩步即可完成。

首先,在啟動(dòng)類或者配置類上添加@EnableAsync,表達(dá)開啟異步執(zhí)行功能;然后,在需要異步執(zhí)行的方法上,添加@Async注解,使方法實(shí)現(xiàn)異步調(diào)用的目標(biāo)。

如果希望采用自定義線程池來執(zhí)行,可以配置一個(gè)線程池對(duì)象并注入到 bean 工廠,最后在異步注解中指定即可;也可以全局配置默認(rèn)線程池。

示例代碼地址:

https://gitee.com/pzblogs/spring-boot-example-demo

責(zé)任編輯:武曉燕 來源: 潘志的研發(fā)筆記
相關(guān)推薦

2010-02-25 09:13:34

WCF異步調(diào)用

2024-07-31 15:57:41

2024-10-15 10:28:43

2025-03-11 00:55:00

Spring停機(jī)安全

2021-09-15 16:20:02

Spring BootFilterJava

2020-01-02 16:30:02

Spring BootJava異步請(qǐng)求

2018-06-21 14:46:03

Spring Boot異步調(diào)用

2022-09-27 12:01:56

Spring異步調(diào)用方式

2009-11-06 15:54:15

WCF異步調(diào)用

2022-09-28 14:54:07

Spring注解方式線程池

2009-07-01 14:23:46

JavaScript異

2009-07-01 14:37:14

JavaScript異

2024-08-01 09:10:03

2009-12-07 14:26:47

WCF異步調(diào)用

2009-10-20 16:48:30

C#委托

2009-08-21 11:24:16

C#異步調(diào)用

2025-02-12 08:07:40

2009-11-09 10:50:30

WCF異步調(diào)用

2009-12-21 14:10:26

WCF異步調(diào)用

2021-03-29 09:26:44

SpringBoot異步調(diào)用@Async
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)