Spring異步任務(wù)配置、執(zhí)行@EnableAsync和@Async

以前在執(zhí)行異步任務(wù)時寫過這樣的代碼:

public class ThreadUtils {
?
       private static final ExecutorService es = Executors.newFixedThreadPool(10);
    ?
       public static void executeAsync(Runnable runnable) {
               es.submit(runnable);
       }
}

在需要使用的地方調(diào)用:

// 異步發(fā)送驗(yàn)證碼
ThreadUtils.executeAsync(()->sendCode(phone, code));

看看Spring是如何處理的

@EnableAsync 啟動Spring的異步方法執(zhí)行功能,此注解和@Configuration一起使用

@Configuration
@EnableAsync
public class AppConfig {

}

@Async 來標(biāo)注要異步執(zhí)行的方法

public class Task {
        @Async
        public void task1(Long id) {
              // do something...
        }
}

也可以在類上使用,在這種情況下,類中的所有方法都被認(rèn)為是異步的。但是請注意,@Configuration配置類中聲明的方法不支持@Async。
@Async在目標(biāo)方法簽名方面,支持任何參數(shù)類型。但是,返回值類型只能是void或Future。后者可以與異步任務(wù)進(jìn)行更豐富的交互,還沒有研究。

Demo 演示

基于 Spring Boot 的 web 程序,模擬用戶注冊時向手機(jī)發(fā)送驗(yàn)證碼,并將驗(yàn)證碼保存到 redis 中用于后續(xù)驗(yàn)證。
發(fā)送驗(yàn)證碼是比較耗時的操作,為了達(dá)到良好的用戶體驗(yàn),需要異步執(zhí)行,減少用戶等待時間。

Application.java

/**
 * @author mafei007
 * @date 2020/3/24 20:50
 */
@SpringBootApplication
// 開啟異步支持
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

controller:

/**
 * @author mafei007
 * @date 2020/3/24 20:52
 */
@RestController
public class TestController {

    private final UserService userService;
    public TestController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/code")
    public String genCode(@RequestParam String phone){
        return userService.genValidationCode(phone);
    }

}

service:

/**
 * @author mafei007
 * @date 2020/3/24 20:55
 */
@Service
public class UserService {
    private final StringRedisTemplate redisTemplate;
    private final SmsUtils smsUtils;

    public UserService(StringRedisTemplate redisTemplate, SmsUtils smsUtils) {
        this.redisTemplate = redisTemplate;
        this.smsUtils = smsUtils;
    }

    public String genValidationCode(String phone){
        // 生成驗(yàn)證碼
        Long code = 123456L;
        // 執(zhí)行異步方法發(fā)送
        smsUtils.sendCode(phone, code);

        // 將驗(yàn)證碼存到 redis,有效期5分鐘,用于驗(yàn)證
        System.out.println("開始保存redis...");
        redisTemplate.opsForValue().set("code_" + phone, code + "", 5, TimeUnit.MINUTES);
        System.out.println("保存 redis 成功...");
        return "success " + phone;
    }
}

utils:

/**
 * @author mafei007
 * @date 2020/3/24 21:19
 */
@Component
public class SmsUtils {

    // 標(biāo)識這個方法異步執(zhí)行
    @Async
    public void sendCode(String phone, Long code){
        System.out.println("開始發(fā)送驗(yàn)證碼...");
        // 模擬調(diào)用接口發(fā)驗(yàn)證碼的耗時
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("發(fā)送成功:" + phone);
    }

}

啟動后訪問接口測試:


接口很快做出響應(yīng)

查看控制臺日志:

開始保存redis...
開始發(fā)送驗(yàn)證碼...
保存 redis 成功...
發(fā)送成功:1507500110

業(yè)務(wù)邏輯中寫的代碼是先發(fā)送驗(yàn)證碼,然后保存redis,而日志中是 redis 保存操作先完成,然后完成發(fā)送。也就是保存 redis 之后就向客戶端發(fā)送了 Response,不用等業(yè)務(wù)順序執(zhí)行完,說明程序異步執(zhí)行成功。

自定義Executor

要想執(zhí)行異步任務(wù),還需要線程池,默認(rèn)情況下Spring會在 ioc容器 中找唯一的org.springframework.core.task.TaskExecutor,或者一個 bean name 為"taskExecutor" 的java.util.concurrent.Executor 作為執(zhí)行任務(wù)的線程池。
如果都沒有的話,會創(chuàng)建 SimpleAsyncTaskExecutor 來處理異步方法調(diào)用.

此外如果 void 返回值的異步方法執(zhí)行中出了異常,異常不會傳播到調(diào)用線程,默認(rèn)情況下由SimpleAsyncUncaughtExceptionHandler 來處理,只是簡單的紀(jì)錄了日志。

實(shí)現(xiàn) AsyncConfigurer 來自定義 Executor 和異常處理:
/**
 * @author mafei007
 * @date 2020/3/24 21:30
 */
@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
         executor.setCorePoolSize(7);
         executor.setMaxPoolSize(42);
         executor.setQueueCapacity(11);
         executor.setThreadNamePrefix("MyExecutor-");
         executor.initialize();
         return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

    /**
     * 處理異步方法中未捕獲的異常
     */
    class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {

            System.out.println("Exception message - " + throwable.getMessage());
            System.out.println("Method name - " + method.getName());
            System.out.println("Parameter values - " + Arrays.toString(obj));
            // do something...
            sendMailToAdmin(throwable.getMessage());
        }

    }

}

其它

@Async注解支持一個String參數(shù),來指定一個bean name,類型是 Executor 或 TaskExecutor ,表示使用 ioc 容器中指定的線程池來執(zhí)行這個異步任務(wù).

public class Task {
    @Async("Executor-001")
    public void task1(Long id) {
        // do something...
    }
}

參考

Spring官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容