性能提升!@Async与CompletableFuture优雅应用

文摘   2024-10-28 08:00   新疆  

最新实战案例锦集:《Spring Boot3实战案例合集》持续更新,每天至少更新一篇文章,订阅后将赠送文章最后展示的所有MD文档(学习笔记)。

环境:SpringBoot3.2.5



1. 简介

@AsyncCompletableFuture 是实现异步处理的强大工具组合。@Async 是Spring框架提供的一个注解,用于标记方法以表明它将在Spring管理的线程池中的另一个线程上异步执行。这使得开发人员能够在不阻塞主线程的情况下执行耗时的任务,从而提高应用程序的整体性能和响应速度。

CompletableFuture 是Java 8引入的一个强大的类,它代表了一个可能尚未完成的计算的结果。CompletableFuture 提供了丰富的API来支持异步编程模式,如回调、组合操作、错误处理等。通过将@AsyncCompletableFuture结合使用,可以实现更高效的异步任务处理。

接下来,我们将介绍@Async与CompletableFuture结合的使用。

2. 实战案例

2.1 @EnableAsync and @Async

Spring 自带 @EnableAsync 注解,可应用于 @Configuration 类以实现异步行为。@EnableAsync 注解会查找标有 @Async 注解的方法,并在后台线程池中运行这些方法。

@Async 注解方法在单独的线程中执行,并返回 CompletableFuture 来保存异步计算的结果。

开启异步功能

@Configuration@EnableAsyncpublic class AsyncConfig {  @Bean(name = "asyncExecutor")  public Executor asyncExecutor()  {    int core = Runtime.getRuntime().availableProcessors() ;    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();    executor.setCorePoolSize(core) ;    executor.setMaxPoolSize(core) ;    executor.setQueueCapacity(100) ;    executor.setThreadNamePrefix("PackAsync-") ;    executor.initialize() ;    return executor ;  }}

如上我们自定义了线程池,该线程池用来执行我们的异步任务。你也可以不用配置,使用系统默认的线程池。

创建异步任务

@Async("asyncExecutor")public CompletableFuture<EmployeeNames> task() {  // TODO}

用 @Async 对方法进行注解,该方法应异步运行。该方法必须是公共的,可以返回值,也可以不返回值。如果返回值,则应使用 Future 接口实现对其进行封装。

这里指定了使用我们自定义的线程池执行异步任务。

多个异步任务同时执行

CompletableFuture.allOf(  asyncMethodOne,   asyncMethodTwo,   asyncMethodThree).join() ;

要合并多个异步任务的结果,通过使用 join() 方法,这将等待所有异步任务执行完成才会继续往后执行。

2.2 Rest Controller中调用异步任务

接下来,我们将创建一个 REST API,从三个远程服务异步获取数据,当所有三个服务的响应都可用时,再汇总响应。

  • 调用/addresses接口获取所有地址信息

  • 调用/phones接口获取所有电话数据

  • 调用/names接口获取所有姓名

  • 等待以上3个接口都返回结果后再进行处理

  • 汇总所有三个应用程序接口的响应,并生成最终响应发送回客户端

     

远程接口准备

@RestControllerpublic class EmployeeController {  @GetMapping("/addresses")  public EmployeeAddresses addresses() {    // TODO  }  @GetMapping("/phones")  public EmployeePhone phones() {    // TODO  }  @GetMapping("/names")  public EmployeeNames names() {    // TODO  }}

我们将通过异步的方式调用上面定义的3个接口。

异步调用REST API

这些服务方法将从远程应用程序接口或数据库中提取数据,必须在不同的线程中并行运行,以加快处理速度。

@Servicepublic class AsyncService {  private static Logger logger = LoggerFactory.getLogger(AsyncService.class);  private final RestTemplate restTemplate;  public AsyncService(RestTemplate restTemplate) {    this.restTemplate = restTemplate ;  }  @Async("asyncExecutor")  public CompletableFuture<EmployeeNames> names()  {    logger.info("getEmployeeName starts");    EmployeeNames employeeNameData = restTemplate.getForObject("http://localhost:8080/names", EmployeeNames.class) ;    logger.info("employeeNameData, {}", employeeNameData) ;    logger.info("employeeNameData completed");    return CompletableFuture.completedFuture(employeeNameData);  }  @Async("asyncExecutor")  public CompletableFuture<EmployeeAddresses> addresses() {    logger.info("getEmployeeAddress starts");    EmployeeAddresses employeeAddressData = restTemplate.getForObject("http://localhost:8080/addresses", EmployeeAddresses.class);    logger.info("employeeAddressData, {}", employeeAddressData) ;    logger.info("employeeAddressData completed");    return CompletableFuture.completedFuture(employeeAddressData);  }  @Async("asyncExecutor")  public CompletableFuture<EmployeePhone> phones() {    logger.info("getEmployeePhone starts") ;    EmployeePhone employeePhoneData = restTemplate.getForObject("http://localhost:8080/phones", EmployeePhone.class) ;    logger.info("employeePhoneData, {}", employeePhoneData) ;    logger.info("employeePhoneData completed") ;    return CompletableFuture.completedFuture(employeePhoneData) ;  }}

注意:你可不能如下方式来执行远程接口的调用

CompletableFuture.supplyAsync(() -> {  return restTemplate.getForObject("http://localhost:8080/phones", EmployeePhone.class) ;}) ;

如果你这样写,你的远程接口并非在你的异步线程中执行,而是在CompletableFuturue的线程池中执行(ForkJoinPool)。

2.3 聚合异步任务

接下来在REST API中调用上面的异步方法、消耗和聚合其响应并返回客户端。

@RestControllerpublic class AsyncController {  private final AsyncService asyncService;  public AsyncController(AsyncService service) {    this.asyncService = asyncService ;  }
@GettMapping("/profile/infos")  public EmployeeDTO infos() throws Exception { CompletableFuture<EmployeeAddresses> addresses = asyncService.addresses() ;    CompletableFuture<EmployeeNames> names = asyncService.names() ;    CompletableFuture<EmployeePhone> phones = asyncService.phones() ; // 等待所有异步任务都执行完成    CompletableFuture.allOf(addresses, names, phones).join() ;    return new EmployeeDTO(addresses.get(), names.get(), phones.get()) ; }}

整个请求的耗时将会是请求最长REST API调用所用的时间,这大大提升该接口的性能。

2.4 异常处理

当方法的返回类型是 Future 时,Future.get() 方法会抛出异常,我们应该在聚合结果之前使用 try-catch 块捕获并处理异常。

问题是,如果异步方法不返回任何值,那么就很难知道方法执行时是否发生了异常。我们可以使用 AsyncUncaughtExceptionHandler 实现来捕获和处理此类异常。

@Configurationpublic class AsyncConfig implements AsyncConfigurer {
@Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler() ; }
  public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {    private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class); @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) {      logger.error("Unexpected asynchronous exception at : "        + method.getDeclaringClass().getName() + "." + method.getName(), ex); } }}

注:异步方法的方法返回值不是Future类型时上面的异常处理才会生效。

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

手写@RefreshScope,很简单嘛!

Spring Boot参数验证这样做!10个技巧你知道几个?

强大!基于Spring Boot动态注册 / 删除Controller接口(支持动态上传)

技巧!Spring Boot生产环境重新初始化Bean

实体与DTO如何转换?这个工具很厉害

强大!Spring Cloud网关Gateway新特性及高级开发技巧

Controller接口地址还能这样玩?

放弃ThreadLocal!TTL真香

请不要自己写!Spring Boot非常实用的内置功能

生产环境Spring Boot切记要关闭这个开关

Spring Boot REST API版本控制的方案及选择

真香!Forest让HTTP请求如此简单

Spring Boot使用强大的FlexyPool动态监控管理数据库连接池

Spring全家桶实战案例源码
spring, springboot, springcloud 案例开发详解
 最新文章