首页 健康生活文章正文

彻底搞懂Web异步编程模型

健康生活 2025年08月16日 02:06 2 admin

长期以来,Spring Web MVC 运行在 Tomcat、JBoss 等 Servlet 容器上,是我们开发 Web 服务的主流框架。但你要注意的是,Servlet 容器是阻塞式的,所以 WebMVC 也建立在阻塞 I/O 之上。

换句话说,任何一个请求的响应过程都是同步的,需要在服务器工作线程接收请求、阻塞等待 I/O 以及完成请求处理之后才能返回。

彻底搞懂Web异步编程模型

这样的同步请求处理机制对普通应用场景来说是合适的,但在一些特定场景下,这种同步机制会存在局限性,需要开发人员采用异步的方式来处理 Web 请求。这就引出了今天我们要讨论的主题,Web 异步编程模型。

让我们先从 Web 异步处理需求和场景开始说起。

Web 异步处理需求和场景

Web 异步处理的第一个应用场景是为了 提升系统性能

我们知道,同步请求处理机制采用的是一个请求对应一个线程的实现过程。这样,系统请求数量越大,我们就需要创建越多的线程,而线程是一种资源,系统的响应能力会随着资源的消耗而逐渐下降。但异步处理机制不需要在处理请求时全程保持某一个线程,这样线程资源就能做到复用。

彻底搞懂Web异步编程模型

接下来是异步处理的第二个应用场景,对于有些请求而言,我们实际上并不关注请求的返回结果,也就是说这些请求采用的是一种 即发即弃(Fire and Forget)模式

这个模式有点类似于消息中间件的处理过程,请求线程发送请求然后直接返回。如果采用同步模式,那么请求必须等待服务端返回。因此,相比于异步处理,同步模式会造成浪费。

彻底搞懂Web异步编程模型

最后,异步处理的第三种场景,在日常开发过程中, 某个请求需要处理大量业务数据,这也是我们会经常碰到的情况。比较典型的例子就是导出数据报表。在这种场景下,如果采用同步模式,很可能会导致出现请求超时。

这时候,合理的解决方案是先对请求做出快速响应,然后再启动异步线程来执行大数据处理逻辑。

彻底搞懂Web异步编程模型

现在来简单总结一下,从三个特定场景的异步模式应用中,我们可以看出:

对于传统请求场景,异步模式能够确保线程复用;

对于即发即弃场景,异步模式能够节省系统资源;

而对于大数量请求场景,异步模式则能够提高用户体验。

所以,如果能够在复杂的业务场景中集成这三种场景中的异步调用机制,我们就可以高效处理 Web 请求。

那么,应该如何使用异步模式来高效应对这些场景呢?Spring 为我们提供了完整的解决方案,我们一起来看一下。

Spring Web 异步编程模型

异步处理的主要优势是调用方不必等待被调用方完成执行过程,这就需要启动新的线程。为了在一个新的线程中执行目标方法,Spring 异步编程模型提供了一个全新的@Async 注解。该注解可以与 JDK 中的 Future 机制以及线程池进行无缝整合。我们先来看这个@Async 注解。

@Async 注解

想要在 Spring 应用程序中启用异步编程模式,我们可以通过@EnableAsync 注解实现这一目标。常见的做法是在 Spring 配置类上添加这一注解。

@Configuration@EnableAsyncpublic class SpringConfig { ... }

@Async 注解支持两种处理模式,即 即发即弃模式和普通的请求响应模式。我们先来看即发即弃模式的代码示例。

@Asyncpublic void recordUserHealthData() {	logger.info("Record user health data successfully.");}

可以看到,我们在一个返回值为 void 的方法上添加了@Async 注解,这样该方法中将以异步的方式进行执行。

然后,我们来看一下请求响应式的异步方式代码示例。

@Servicepublic class HealthService {    @Async    public Future<String> getHealthDescription() throws InterruptedException {        LOGGER.info("Thread id: " + Thread.currentThread().getId());        //睡眠 2 秒        Thread.sleep(2000);        String healthDescription = “health description”;        LOGGER.info(processInfo);        return new AsyncResult<String>(healthDescription);    }}

可以看到,这里我们在方法入口打印了当前的线程 ID,然后让主线程睡眠 2 秒用来模拟长时间的业务处理流程。接着,我们返回异步调用的结果对象 AsyncResult。

AsyncResult 是 Spring 框架对 JDK 中 Future 接口的一种实现,我们可以通过 AsyncResult 对象跟踪异步调用的结果。为了更好理解上述方法的执行过程,我们有必要先来看看 JDK 中的 Future 对象。

传统模式调用和 Future 模式调用的对比可以参考图 5。我们看到在 Future 模式调用过程中,客户端在向服务器端发起请求之后马上返回,可以继续执行其他任务直到服务器端通知 Future 调用的结果,体现了 Future 调用异步化特点。

彻底搞懂Web异步编程模型

但原生的 Future 也有同步等待问题,因为通过 Future 对象直接获取调用结果同样会导致线程等待。为了解决这个问题,Java 8 中引入了 CompletableFuture 对原生的 Future 进行了优化,可以直接通过 CompletableFuture 将异步执行结果交给另外一个异步线程来处理。这样在异步任务完成后,我们在获取任务结果时则不需要等待。

例如,如果想要在异步执行任务完成之后返回值,那么可以使用 CompletableFuture 的 supplyAsync() 方法,示例代码如下所示。

@RequestMapping(value = "/health_description")public CompletableFuture<String> syncHealthDescription () {	CompletableFuture.supplyAsync(new Supplier<String>() {           @Override           public String get() {               try {                   return healthService.getHealthDescription().get();               } catch (InterruptedException | ExecutionException e) {                   LOGGER.error(e);               }               return "No health description found";           }        });        return completableFuture;}

WebAsyncTask

前面介绍的@Async 注解实际上是通用的,我们可以用它来完成包含 Web 请求在内的任意场景下的异步处理流程。而随着 Spring Boot 的诞生,也出现了 WebAsyncTask 这一专门针对 Web 场景下的异步执行组件。

相较@Async 注解,WebAsyncTask 为开发人员提供了更灵活的异步任务处理机制,并内置了异步回调、超时处理和异常处理。如果想要初始化一个 WebAsyncTask 对象,我们需要设置一个超时时间,并启动一个线程对象。

public WebAsyncTask(long timeout, Callable<V> callable)

基于这一使用方式,我们先来看一下 WebAsyncTask 的简单示例。

@RequestMapping(value = "task_normal", method = RequestMethod.GET)public WebAsyncTask<String> task1() {        System.out.println("The main Thread name is " +Thread.currentThread().getName());        // 此处模拟开启一个异步任务       WebAsyncTask<String> task1 = new WebAsyncTask<String>(4 * 1000L, () -> {           System.out.println("The first Thread name is " +Thread.currentThread().getName());           Thread.sleep(2 * 1000L);           return "task1 executed!";        });        // 任务执行完成时调用该方法        task1.onCompletion(() -> {           System.out.println("task1 finished!");        });        // 可以继续执行其他操作        System.out.println("task1 can do other things!");        return task1;}

可以看到,这里初始化了一个 WebAsyncTask 对象,并设置任务的超时时间为 4s。异步任务执行采用 Thread.sleep 方法来进行模拟,这里设置异步线程的睡眠时间为 2s。然后,我们还通过 WebAsyncTask 的 onCompletion() 方法指定了任务执行完成时的回调函数。

执行以上代码,我们在控制台可以得到如下日志信息。

The main Thread name is http-nio-7000-exec-5task1 can do other things!The first Thread name is MvcAsync2task1 finished!

显然,我们先打印出了主线程的名称,然后主线程可以继续执行并返回结果。然后我们启动异步线程,并打印出该线程的名称。当异步线程执行完毕时,同样打印出了这一信息。如果你在浏览器中访问这个 HTTP 端点,那么可以获取异步方法的正常返回值"task1 executed!"。

我们接着来看一下如何设置异常处理回调的方法,示例代码如下所示。

@RequestMapping(value = "task_error", method = RequestMethod.GET)public WebAsyncTask<String> getUserWithError() {        System.out.println("The main Thread name is "+ Thread.currentThread().getName());        // 此处模拟开启一个异步任务       WebAsyncTask<String> task3 = new WebAsyncTask<String>(4 * 1000L, () -> {           System.out.println("The second Thread name is "+ Thread.currentThread().getName());           int num = 1 / 0;           System.err.println(num);           return "";        });        // 发生异常时调用该方法        task3.onError(() -> {           System.err.println(Thread.currentThread().getName());           System.err.println("task3 error occured!");           return "";        });        // 任务执行完成时调用该方法        task3.onCompletion(() -> {           System.out.println("task3 finished!");        });        // 可以继续执行其他操作        System.out.println("task3 can do other things!");        return task3;}

这里设置了一个 onError() 回调,并通过除 0 操作触发了这一回调,结果如下所示。

The main Thread name is http-nio-7000-exec-10task3 can do other things!The second Thread name is MvcAsync4http-nio-7000-exec-1task3 error occured!task3 finished!

这样,基于 WebAsyncTask 的异步编程模型就介绍完毕了。从上文中我们可以看出,WebAsyncTask 除了能够实现异步调用,它所提供的异步编程模型充分考虑了异步执行过程中可能出现的异常情况和超时机制。同时,基于回调的异步处理结果的获取过程也显得非常自然。相比@Async 注解,WebAsyncTask 的功能更加强大。

所以,在日常开发过程中,我建议你使用这个工具类来实现对 Web 请求的异步处理。

总结

今天我们系统分析了在 Web 应用程序开发过程中,如何使用 Spring 框架提供的异步编程能力来提高系统的响应性。

我们从异步处理场景讲起,引出 Spring 中所提供了@Async 注解,该注解是对异步处理过程的抽象。在具体使用过程中,我们一般结合 CompletableFuture 来处理异步线程之间的交互过程。同时,针对 Web 开发场景,Spring 还专门提供了一个 WebAsyncTask 工具类来简化开发过程。

在日常开发过程中,@Async 注解为开发人员提供的是一种通用型的异步编程,我们可以使用它在应用程序的各层组件中添加异步处理机制。而 WebAsyncTask 则专门面向 Web 请求处理,因此,如果你正在开发 Web 应用程序,那么 WebAsyncTask 无疑是你的首选。

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动