SpringBoot异步调用

一、概述

在程序执行时候还有一个瓶颈,串行执行,可以通过使用不同线程类快速提升应用的速度。

要启用Spring的异步功能,必须要使用@EnableAsync注解。这样将会透明地使用java.util.concurrent.Executor来执行所有带有@Async注解的方法。@Async所修饰的函数不要定义为static类型,这样异步调用不会生效。

针对调用的Async,如果不做Future特殊处理,执行完调用方法会立即返回结果,如异步邮件发送,不会真的等邮件发送完毕才响应客户,如需等待可以使用Future阻塞处理。

二、异步实现 - EnableAsync

2.1 核心注解

SpringBoot中将一个方法声明为异步方法非常简单,只需两个注解即可@EnableAsync@Async

  • @EnableAsync:用于开启SpringBoot支持异步的功能,用在SpringBoot的启动类上。
  • @Async:用于方法上,标记该方法为异步处理方法。

需要注意的是@Async并不支持用于被@Configuration注解的类的方法上。同一个类中,一个方法调用另外一个有@Async的方法,注解也是不会生效的。

2.2 使用示例

1、开启异步功能

main方法增加@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        System.out.println("ThreadId:" + Thread.currentThread().getId());
    }
}

2、异步方法

在所需方法增加@Async注解

@Component
public class Task {
    @Async
    public void doTaskOne() throws Exception {
        for (int i = 0; i < 3; i++) {
            Thread.sleep(200);
            System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskOne");
        }
    }

    @Async
    public void doTaskTwo() throws Exception {
        for (int i = 0; i < 3; i++) {
            Thread.sleep(200);
            System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskTwo");
        }
    }

    @Async
    public void doTaskThree() throws Exception {
        for (int i = 0; i < 3; i++) {
            Thread.sleep(200);
            System.out.println("ThreadId:" + Thread.currentThread().getId() + ":doTaskThree");
        }
    }
}

@Async注解的使用与Callable有类似之处,在默认情况下使用的都是SimpleAsyncTaskExecutor线程池,可参考Callable中的方式来自定义线程池。

3、查看调用

@RestController
@RequiredArgsConstructor
public class TestAsyns {

    private final Task task;

    @RequestMapping("/testAsync")
    public ResponseEntity testAsync() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
        return ResponseEntity.ok("ok");
    }
}

上述方法依次调用三个方法。

如果去除@EnableAsync注解,输出如下:【可见是串行执行】

ThreadId:33:doTaskOne
ThreadId:33:doTaskOne
ThreadId:33:doTaskOne
ThreadId:33:doTaskTwo
ThreadId:33:doTaskTwo
ThreadId:33:doTaskTwo
ThreadId:33:doTaskThree
ThreadId:33:doTaskThree
ThreadId:33:doTaskThree

如果增加@EnableAsync注解,输出如下:【可见是并行执行】

ThreadId:56:doTaskThree
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:56:doTaskThree
ThreadId:54:doTaskOne
ThreadId:56:doTaskThree
ThreadId:55:doTaskTwo

三、自定义异步线程池

Spring默认使用SimpleAsyncTaskExecutor执行异步任务,该线程池不重用线程(每次调用创建新线程),在高并发场景下会导致线程泛滥,严重影响系统性能。因此,生产环境必须自定义线程池,通过合理的线程池参数配置,实现资源的高效利用。

3.1 线程池核心参数

在配置线程池前,需理解核心参数的含义:​

  • corePoolSize:核心线程数,线程池维护的最小线程数量,即使线程空闲也不会销毁​
  • maxPoolSize:最大线程数,线程池可创建的最大线程数量​
  • queueCapacity:任务队列容量,核心线程满时,任务会先进入队列等待​
  • keepAliveSeconds:非核心线程空闲存活时间,超过该时间会被销毁​
  • threadNamePrefix:线程名称前缀,便于日志排查​
  • rejectedExecutionHandler:任务拒绝策略,当线程池和队列都满时的处理方式

3.2 实现方式

3.2.1 注入Bean方式

适用于简单场景,通过@Bean注解创建线程池实例:

import java.util.concurrent.Executor;  
  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.annotation.EnableAsync;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;  
  
@Configuration  
public class ThreadConfig  {  
  
     // 执行需要依赖线程池,这里就来配置一个线程池  
     @Bean  
     public Executor getExecutor() {  
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
          executor.setCorePoolSize(5);  
          executor.setMaxPoolSize(10);  
          executor.setQueueCapacity(25);  
          executor.initialize();  
          return executor;  
     }  
}

3.2.2 实现AsyncConfigurer接口

适用于复杂场景,可同时配置线程池和异常处理器:

@Configuration
@Slf4j
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        //做好不超过10个,这里写两个方便测试
        return Executors.newFixedThreadPool(2);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex,method,params) -> log.error("Uncaught async error", ex);
    }
}

Executor的初始化配置,还有很多种,可以参看https://www.cnblogs.com/bjlhx/category/1086008.html

3.3 Spring内置线程池

Spring已经实现的异步线程池:

  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
  3. ConcurrentTaskExecutor(不推荐):Executor的适配类。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
  5. ThreadPoolTaskExecutor(推荐):最常使用,其实质是对java.util.concurrent.ThreadPoolExecutor的包装。

使用上述配置,能够确保在应用中,用来处理异步任务的线程不会超过10个。这对于web应用很重要,因为每个客户端都会有一个专用的线程。你所使用的线程越多,阻塞时间越长那么能够处理的客户端就会越少。

如果设置成两个,程序中有3个异步线程,也会只有两个运行,如下:

ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskTwo
ThreadId:54:doTaskOne
ThreadId:55:doTaskThree
ThreadId:55:doTaskThree
ThreadId:55:doTaskThree

四、异步结果处理

4.1 Callable

CallableJava并发包中的接口,用于返回异步执行结果,适用于简单的异步结果获取场景。

基于Callable的处理流程如下:

  1. Spring MVC开启副线程处理业务(将Callable提交到TaskExecutor);
  2. DispatcherServlet和所有的Filter退出Web容器的线程,但是response保持打开状态;
  3. Callable返回结果,SpringMVC将原始请求重新派发给容器(再重新请求一次/email),恢复之前的处理;
  4. DispatcherServlet重新被调用,将结果返回给用户;

代码实现示例如下:

@GetMapping("/email")
public Callable<String> order() {
    System.out.println("主线程开始:" + Thread.currentThread().getName());
    Callable<String> result = () -> {
        System.out.println("副线程开始:" + Thread.currentThread().getName());
        Thread.sleep(1000);
        System.out.println("副线程返回:" + Thread.currentThread().getName());
        return "success";
    };

    System.out.println("主线程返回:" + Thread.currentThread().getName());
    return result;
}

访问对应URL,控制台输入日志如下:

主线程开始:http-nio-8080-exec-1
主线程返回:http-nio-8080-exec-1
副线程开始:task-1
副线程返回:task-1

通过日志可以看出,主线程已经完成了,副线程才进行执行。同时,URL返回结果“success”。这也说明一个问题,服务器端的异步处理对客户端来说是不可见的。

Callable默认使用SimpleAsyncTaskExecutor类来执行,这个类非常简单而且没有重用线程。在实践中,需要使用AsyncTaskExecutor类来对线程进行配置。

这里通过实现WebMvcConfigurer接口来完成线程池的配置。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

    /**
     * 配置线程池
     */
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(2);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("thread-pool-");
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        // 处理callable超时
        configurer.setDefaultTimeout(60 * 1000);
        configurer.setTaskExecutor(myThreadPoolTaskExecutor);
        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }
}

4.2 使用Future + 轮询(不推荐)

FutureJDK 1.5引入的接口,用于表示异步任务的结果。通过isDone()方法轮询任务是否完成,适用于简单的多任务结果聚合场景,但轮询会浪费CPU资源,不推荐在高并发场景使用。

实现示例

  1. 带 Future 返回的异步任务
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.AsyncResult;
import java.util.concurrent.Future;

@Component
public class FutureAsyncTask {

   /**
    * 异步任务1:返回Future结果
    */
   @Async
   public Future<String> executeTaskOne() throws InterruptedException {
       Thread.sleep(1000);
       System.out.printf("TaskOne completed. Thread: %d%n", Thread.currentThread().getId());
       return new AsyncResult<>("TaskOne Result");
   }

   /**
    * 异步任务2:返回Future结果
    */
   @Async
   public Future<String> executeTaskTwo() throws InterruptedException {
       Thread.sleep(1000);
       System.out.printf("TaskTwo completed. Thread: %d%n", Thread.currentThread().getId());
       return new AsyncResult<>("TaskTwo Result");
   }

   /**
    * 异步任务3:返回Future结果
    */
   @Async
   public Future<String> executeTaskThree() throws InterruptedException {
       Thread.sleep(1000);
       System.out.printf("TaskThree completed. Thread: %d%n", Thread.currentThread().getId());
       return new AsyncResult<>("TaskThree Result");
   }
}
  1. Future 结果聚合接口
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.Future;

@RestController
@RequiredArgsConstructor
public class FutureAsyncController {

   private final FutureAsyncTask futureAsyncTask;

   /**
    * 测试Future结果聚合
    */
   @RequestMapping("/testFutureAsync")
   public ResponseEntity<String> testFutureAsync() throws Exception {
       long startTime = System.currentTimeMillis();
       // 提交三个异步任务
       Future<String> taskOneResult = futureAsyncTask.executeTaskOne();
       Future<String> taskTwoResult = futureAsyncTask.executeTaskTwo();
       Future<String> taskThreeResult = futureAsyncTask.executeTaskThree();
       // 轮询等待所有任务完成(不推荐,浪费CPU资源)
       while (!(taskOneResult.isDone() && taskTwoResult.isDone() && taskThreeResult.isDone())) {
           // 空轮询,可添加短暂睡眠减少CPU占用
           Thread.sleep(10);
       }

       // 获取任务结果
       String result = String.format("""
               TaskOne Result: %s
               TaskTwo Result: %s
               TaskThree Result: %s
               Total Time: %dms""",
               taskOneResult.get(),
               taskTwoResult.get(),
               taskThreeResult.get(),
               System.currentTimeMillis() - startTime);
       return ResponseEntity.ok(result);
   }
}

执行效果

总耗时约 1000ms(并行执行),结果如下:

TaskOne completed. Thread: 54
TaskTwo completed. Thread: 55
TaskThree completed. Thread: 56
TaskOne Result: TaskOne Result
TaskTwo Result: TaskTwo Result
TaskThree Result: TaskThree Result
Total Time: 1020ms

4.3 Spring的ListenableFuture和CountDownLatch处理

ListenableFutureSpring提供的增强型Future,支持添加回调函数(SuccessCallbackFailureCallback),结合CountDownLatch可实现高效的多任务结果聚合,避免轮询带来的CPU浪费。

核心组件说明​

  • ListenableFuture:继承自Future,支持添加回调函数​
  • CountDownLatch:同步工具类,用于等待多个线程完成操作(初始化计数器,线程完成后调用countDown(),主线程调用await()等待)​
  • SuccessCallback:任务成功完成时的回调接口​
  • FailureCallback:任务异常时的回调接口
  1. ListenableFuture 异步服务
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class ListenableFutureSearchService {

    /**
     * 异步搜索:模拟 Elasticsearch 查询
     */
    @Async
    public ListenableFuture<String> asyncSearch(String keyword) {
        log.info("Searching keyword: {} by thread: {}", keyword, Thread.currentThread().getName());
        try {
            // 模拟搜索耗时
            Thread.sleep(2000);
            return new AsyncResult<>("Search result for [" + keyword + "]");
        } catch (InterruptedException e) {
            log.error("Search interrupted for keyword: {}", keyword, e);
            // 封装异常结果
            return new AsyncResult<>(null);
        }
    }
}
  1. 结果聚合服务
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class ListenableFutureAggregateService {

    private final ListenableFutureSearchService searchService;

    /**
     * 批量搜索:聚合多个关键词的搜索结果
     */
    public List<String> batchSearch(List<String> keywords) throws InterruptedException {
        // 初始化计数器:关键词数量
        CountDownLatch latch = new CountDownLatch(keywords.size());
        // 线程安全的结果列表
        List<String> searchResults = Collections.synchronizedList(new ArrayList<>());

        for (String keyword : keywords) {
            ListenableFuture<String> future = searchService.asyncSearch(keyword);
            // 添加回调函数
            future.addCallback(new ListenableFutureCallback<>() {
                @Override
                public void onSuccess(String result) {
                    if (result != null) {
                        searchResults.add(result);
                    }
                    // 计数器减1
                    latch.countDown();
                }

                @Override
                public void onFailure(Throwable ex) {
                    // 异常处理
                    searchResults.add("Search failed for [" + keyword + "]: " + ex.getMessage());
                    latch.countDown();
                }
            });
        }

        // 等待所有任务完成,最多等待10秒
        boolean allCompleted = latch.await(10, TimeUnit.SECONDS);
        if (!allCompleted) {
            searchResults.add("Some search tasks are timeout");
        }

        return searchResults;
    }
}

测试接口

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;

@RestController
@RequiredArgsConstructor
public class ListenableFutureController {

    private final ListenableFutureAggregateService aggregateService;

    /**
     * 测试ListenableFuture批量搜索
     */
    @RequestMapping("/testListenableFuture")
    public ResponseEntity<List<String>> testListenableFuture() throws InterruptedException {
        List<String> keywords = Arrays.asList("Java", "Spring", "Async");
        List<String> results = aggregateService.batchSearch(keywords);
        return ResponseEntity.ok(results);
    }
}

执行效果​

总耗时约 2000ms,结果如下:

[
  "Search result for [Java]",
  "Search result for [Spring]",
  "Search result for [Async]"
]

4.4 使用CompletableFuture(推荐)

CompletableFutureJDK 8引入的增强型Future,实现了FutureCompletionStage接口,支持链式调用、结果组合、异常处理等高级特性,是目前异步编程的最佳实践。

实现示例​

  1. CompletableFuture 异步服务
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class GitHubLookupService {

    private final RestTemplate restTemplate;

    // 构造函数注入RestTemplate
    public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    /**
     * 异步查询GitHub用户信息
     */
    @Async
    public CompletableFuture<User> findGitHubUser(String username) throws InterruptedException {
        log.info("Looking up GitHub user: {} by thread: {}", username, Thread.currentThread().getName());
        
        // 构建GitHub API URL
        String url = String.format("https://api.github.com/users/%s", username);
        
        // 同步调用GitHub API(实际项目建议使用异步HTTP客户端)
        User user = restTemplate.getForObject(url, User.class);
        
        // 模拟处理耗时
        Thread.sleep(1000);
        
        // 返回已完成的CompletableFuture
        return CompletableFuture.completedFuture(user);
    }
}
  1. User 实体类
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

/**
 * GitHub用户实体类
 * JsonIgnoreProperties:忽略未知的JSON字段
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    private String name;       // 用户名
    private String blog;       // 博客地址
    private String location;   // 所在地
    private int publicRepos;   // 公开仓库数量
}
  1. 测试接口
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
public class CompletableFutureController {

    private final GitHubLookupService gitHubLookupService;

    /**
     * 测试CompletableFuture批量查询GitHub用户
     */
    @RequestMapping("/testCompletableFuture")
    public ResponseEntity<String> testCompletableFuture() throws Exception {
        long startTime = System.currentTimeMillis();

        // 1. 提交三个异步任务
        CompletableFuture<User> user1 = gitHubLookupService.findGitHubUser("PivotalSoftware");
        CompletableFuture<User> user2 = gitHubLookupService.findGitHubUser("CloudFoundry");
        CompletableFuture<User> user3 = gitHubLookupService.findGitHubUser("Spring-Projects");

        // 2. 等待所有任务完成(非阻塞)
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(user1, user2, user3);

        // 3. 所有任务完成后,处理结果(链式调用)
        String result = allFutures.thenApply(v -> {
            try {
                return "GitHub User Info:n" +
                        String.format("1. %s (Blog: %s, Repos: %d)%n", 
                                user1.get().getName(), user1.get().getBlog(), user1.get().getPublicRepos()) +
                        String.format("2. %s (Blog: %s, Repos: %d)%n", 
                                user2.get().getName(), user2.get().getBlog(), user2.get().getPublicRepos()) +
                        String.format("3. %s (Blog: %s, Repos: %d)%n", 
                                user3.get().getName(), user3.get().getBlog(), user3.get().getPublicRepos()) +
                        String.format("Total Time: %dms", System.currentTimeMillis() - startTime);
            } catch (Exception e) {
                return "Error processing user info: " + e.getMessage();
            }
        }).get(); // 获取最终结果

        return ResponseEntity.ok(result);
    }
}
  1. 配置类(RestTemplate 和线程池)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class CompletableFutureConfig {

    /**
     * 配置RestTemplate
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    /**
     * 配置CompletableFuture专用线程池
     */
    @Bean(name = "completableFutureExecutor")
    public Executor completableFutureExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("CompletableFuture-Thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

执行效果​
总耗时约 1000ms,结果如下:

GitHub User Info:
1. Pivotal Software, Inc. (Blog: https://pivotal.io/, Repos: 156)
2. Cloud Foundry (Blog: https://www.cloudfoundry.org/, Repos: 228)
3. Spring (Blog: https://spring.io/, Repos: 112)
Total Time: 1050ms

五、基于WebAsyncTask实现

WebAsyncTaskSpring提供的Callable封装类,在Callable的基础上增加了超时处理、完成回调、异常处理等功能,适用于Web环境下的异步请求处理。

5.1 核心特性​

  1. 超时回调:设置任务超时时间,超时后执行指定逻辑​
  2. 完成回调:任务完成后(无论成功或失败)执行的逻辑​
  3. 异常处理:可自定义异常处理策略​
  4. 线程池指定:支持指定专用线程池​

5.2 实现示例​

  1. WebAsyncTask控制器​
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class WebAsyncTaskController {

    /**
     * WebAsyncTask示例:模拟订单处理
     */
    @GetMapping("/processOrder")
    public WebAsyncTask<String> processOrder() {
        log.info("Request received by thread: {}", Thread.currentThread().getName());

        // 创建WebAsyncTask,设置超时时间为60秒
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(60 * 1000L, () -> {
            log.info("Order processing started by thread: {}", Thread.currentThread().getName());
            // 模拟订单处理耗时
            Thread.sleep(3000);
            // 模拟随机异常
            if (Math.random() > 0.5) {
                throw new RuntimeException("Order processing failed");
            }
            log.info("Order processing completed");
            return "Order processed successfully";
        });

        // 超时回调:任务超时后执行
        webAsyncTask.onTimeout(() -> {
            log.error("Order processing timeout");
            return "Order processing timeout, please try again later";
        });

        // 完成回调:任务完成后执行(无论成功或失败)
        webAsyncTask.onCompletion(() -> {
            log.info("Order processing task completed (success or failure)");
        });

        return webAsyncTask;
    }
}
​```

2. WebAsyncTask线程池配置​

​```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebAsyncTaskConfig implements WebMvcConfigurer {

    /**
     * 配置WebAsyncTask专用线程池
     */
    @Bean(name = "webAsyncTaskExecutor")
    public ThreadPoolTaskExecutor webAsyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("WebAsyncTask-Thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 配置WebAsyncTask支持
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 设置WebAsyncTask超时时间
        configurer.setDefaultTimeout(60 * 1000);
        // 设置专用线程池
        configurer.setTaskExecutor(webAsyncTaskExecutor());
    }
}
​```

## 5.3 执行效果​

正常执行日志​
​
```text
Request received by thread: http-nio-8080-exec-1​
Order processing started by thread: WebAsyncTask-Thread-1​
Order processing completed​
Order processing task completed (success or failure)​
​```

超时执行日志​
​
```text
Request received by thread: http-nio-8080-exec-2​
Order processing started by thread: WebAsyncTask-Thread-2​
Order processing timeout​
Order processing task completed (success or failure)​
​```

异常执行日志​
​
```text
Request received by thread: http-nio-8080-exec-3​
Order processing started by thread: WebAsyncTask-Thread-3​
Order processing task completed (success or failure)​
[Exception] RuntimeException: Order processing failed​

六、DeferredResult实现

DeferredResultSpring提供的高级异步工具,与Callable的主要区别在于:DeferredResult的结果可以由其他线程(如MQ消费者、定时任务、外部系统回调)设置,支持跨线程通信,适用于实现长轮询、服务端推送、订单状态查询等场景。

6.1 核心特性

  1. 跨线程结果设置:结果可由非请求线程设置(如 MQ 消息触发)
  2. 超时处理:支持设置超时时间,超时后返回默认结果
  3. 完成回调:支持添加结果设置后的回调函数
  4. 取消处理:支持任务取消后的处理逻辑

6.2 典型应用场景

  1. 长轮询:客户端发起请求后,服务器 hold 住连接,有数据更新时再返回响应
  2. 订单状态查询:客户端发起订单状态查询,服务器等待订单状态变更后返回结果
  3. 服务端推送:模拟 WebSocket,实现简单的服务端数据推送
  4. 异步通知处理:等待外部系统回调(如支付回调)后返回结果

6.3 实现示例:长轮询订单状态查询

1. DeferredResult 控制器

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class DeferredResultOrderController {

   // 存储订单ID与DeferredResult的映射(线程安全)
   private final Map<String, DeferredResult<String>> orderDeferredResultMap = new ConcurrentHashMap<>();

   /**
    * 长轮询查询订单状态
    * @param orderId 订单ID
    * @return DeferredResult
    */

   @GetMapping("/queryOrderStatus/{orderId}")
   public DeferredResult<String> queryOrderStatus(@PathVariable String orderId) {
       log.info("Received order status query for orderId: {}, thread: {}",
               orderId, Thread.currentThread().getName());

       // 1. 创建DeferredResult,设置超时时间为30秒
       DeferredResult<String> deferredResult = new DeferredResult<>(30 * 1000L,
               "Order status query timeout, please try again later");

       // 2. 存储订单ID与DeferredResult的映射
       orderDeferredResultMap.put(orderId, deferredResult);

       // 3. 完成回调:移除映射,释放资源
       deferredResult.onCompletion(() -> {
           orderDeferredResultMap.remove(orderId);
           log.info("Order status query completed for orderId: {}, thread: {}",
                   orderId, Thread.currentThread().getName());
       });

       // 4. 超时回调:记录超时日志
       deferredResult.onTimeout(() -> {
           log.warn("Order status query timeout for orderId: {}", orderId);
       });

       // 5. 取消回调:处理客户端取消请求
       deferredResult.onError((ex) -> {
           log.error("Order status query error for orderId: {}", orderId, ex);
       });
       return deferredResult;
   }

   /**
    * 模拟订单状态更新(如MQ消息触发、定时任务调用)
    * @param orderId 订单ID
    * @param status 新状态
    */
   @GetMapping("/updateOrderStatus/{orderId}/{status}")
   public String updateOrderStatus(@PathVariable String orderId, @PathVariable String status) {
       log.info("Updating order status: orderId={}, status={}, thread: {}",
               orderId, status, Thread.currentThread().getName());
       // 1. 获取订单对应的DeferredResult
       DeferredResult<String> deferredResult = orderDeferredResultMap.get(orderId);
       if (deferredResult != null && !deferredResult.isSetOrExpired()) {
           // 2. 设置订单状态结果(唤醒等待的请求)
           String result = String.format("Order status updated: orderId=%s, status=%s", orderId, status);
           deferredResult.setResult(result);
           return "Order status updated successfully";
       } else {
           return "No pending query for this order or query has expired";
       }
   }
}

2. DeferredResult 配置

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.annotation.Bean;

@Configuration
public class DeferredResultConfig implements WebMvcConfigurer {

   /**
    * 配置DeferredResult专用线程池
    */
   @Bean(name = "deferredResultExecutor")
   public ThreadPoolTaskExecutor deferredResultExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(4);
       executor.setMaxPoolSize(8);
       executor.setQueueCapacity(50);
       executor.setThreadNamePrefix("DeferredResult-Thread-");
       executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       executor.initialize();
       return executor;
   }

   /**
    * 配置DeferredResult支持
    */
   @Override
   public void configureAsyncSupport(AsyncSupportConfigurer configurer) {

       // 设置默认超时时间
       configurer.setDefaultTimeout(30 * 1000);

       // 设置专用线程池
       configurer.setTaskExecutor(deferredResultExecutor());
   }
}

6.4 执行流程与效果

1. 客户端发起订单状态查询

GET /queryOrderStatus/ORDER123

服务器日志:

Received order status query for orderId: ORDER123, thread: http-nio-8080-exec-1

此时客户端连接被 hold 住,等待订单状态更新。

2. 触发订单状态更新

GET /updateOrderStatus/ORDER123/PAID

服务器日志:

Updating order status: orderId=ORDER123, status=PAID, thread: http-nio-8080-exec-2
Order status query completed for orderId: ORDER123, thread: DeferredResult-Thread-1

3. 客户端接收响应

客户端收到订单状态更新结果:

Order status updated: orderId=ORDER123, status=PAID

4. 超时场景

若 30 秒内未触发订单状态更新,客户端收到超时响应:

Order status query timeout, please try again later

服务器日志:

Order status query timeout for orderId: ORDER123
Order status query completed for orderId: ORDER123, thread: DeferredResult-Thread-2

七、拓展

7.1 @Async、WebAsyncTask、Callable、DeferredResult的区别

所在的包不同:

  • @Async:org.springframework.scheduling.annotation;
  • WebAsyncTask:org.springframework.web.context.request.async;
  • Callable:java.util.concurrent;
  • DeferredResult:org.springframework.web.context.request.async;

通过所在的包,我们应该隐隐约约感到一些区别,比如@Async是位于scheduling包中,而WebAsyncTaskDeferredResult是用于Web(Spring MVC)的,而Callable是用于concurrent(并发)处理的。

对于Callable,通常用于Controller方法的异步请求,当然也可以用于替换Runnable的方式。在方法的返回上与正常的方法有所区别:

// 普通方法
public String aMethod(){
}

// 对照Callable方法
public Callable<String> aMethod(){
}

WebAsyncTask是对Callable的封装,提供了一些事件回调的处理,本质上区别不大。

  • DeferredResult使用方式与Callable类似,重点在于跨线程之间的通信。
  • @Async也是替换Runnable的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller层,而可以是任何层的方法上。

当然,大家也可以从返回结果,异常处理等角度来分析一下,这里就不再展开了。

posted @ 2022-10-11 18:04  夏尔_717  阅读(769)  评论(0)    收藏  举报