SpringBoot中如何实现异步请求的异步开发?

2026-05-21 02:314阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1568个文字,预计阅读时间需要7分钟。

SpringBoot中如何实现异步请求的异步开发?

Servlet 3.0之前,Servlet使用线程池处理每个请求,每个HTTP请求都由一个线程从头到尾负责处理。如果请求需要执行I/O操作,例如访问数据库或调用第三方服务,处理过程可能会受到影响。

何为异步请求

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。其请求流程大致为:

而在Servlet3.0发布后,提供了一个新特性:异步处理请求。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。其请求流程为:

SpringBoot中如何实现异步请求的异步开发?

在Servlet 3.0后,我们可以从HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。


多说几句:

随着Spring5发布,提供了一个响应式Web框架:Spring WebFlux。之后可能就不需要Servlet容器的支持了。以下是其先后对比图:

左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。

对于其发展前景还是拭目以待吧。有时间也该去了解下Spring5了。


原生异步请求API说明

在编写实际代码之前,我们来了解下一些关于异步请求的api的调用说明。

  • 获取AsyncContext:根据HttpServletRequest对象获取。
AsyncContext asyncContext = request.startAsync();
  • 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理

其监听器的接口代码:

public interface AsyncListener extends EventListener { void onComplete(AsyncEvent event) throws IOException; void onTimeout(AsyncEvent event) throws IOException; void onError(AsyncEvent event) throws IOException; void onStartAsync(AsyncEvent event) throws IOException; }

说明:

  • 1.onStartAsync:异步线程开始时调用
  • 2.onError:异步线程出错时调用
  • 3.onTimeout:异步线程执行超时调用
  • 4.onComplete:异步执行完毕时调用

一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。

 

需要注意的是只有在调用request.startAsync前将监听器添加到AsyncContext,监听器的onStartAsync方法才会起作用,而调用startAsync前AsyncContext还不存在,所以第一次调用startAsync是不会被监听器中的onStartAsync方法捕获的,只有在超时后又重新开始的情况下onStartAsync方法才会起作用。

  • 设置超时:通过setTimeout方法设置,单位:毫秒。

一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。

Servlet方式实现异步请求

前面已经提到,可通过HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文。所以,我们来实际操作下。

  • 0.编写一个简单控制层
/** * 使用servlet方式进行异步请求 * @author oKong * */ @Slf4j @RestController public class ServletController { @RequestMapping("/servlet/orig") public void todo(HttpServletRequest request, HttpServletResponse response) throws Exception { //这里来个休眠 Thread.sleep(100); response.getWriter().println("这是的请求返回"); } @RequestMapping("/servlet/async") public void todoAsync(HttpServletRequest request, HttpServletResponse response) { AsyncContext asyncContext = request.startAsync(); asyncContext.addListener(new AsyncListener() { @Override public void onTimeout(AsyncEvent event) throws IOException { log.info("超时了:"); //做一些超时后的相关操作 } @Override public void onStartAsync(AsyncEvent event) throws IOException { // TODO Auto-generated method stub log.info("线程开始"); } @Override public void onError(AsyncEvent event) throws IOException { log.info("发生错误:",event.getThrowable()); } @Override public void onComplete(AsyncEvent event) throws IOException { log.info("执行完成"); //这里可以做一些清理资源的操作 } }); //设置超时时间 asyncContext.setTimeout(200); //也可以不使用start 进行异步调用 // new Thread(new Runnable() { // // @Override // public void run() { // 编写业务逻辑 // // } // }).start(); asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(100); log.info("内部线程:" + Thread.currentThread().getName()); asyncContext.getResponse().setCharacterEncoding("utf-8"); asyncContext.getResponse().setContentType("text/html;charset=UTF-8"); asyncContext.getResponse().getWriter().println("这是的请求返回"); } catch (Exception e) { log.error("异常:",e); } //异步请求完成通知 //此时整个请求才完成 //其实可以利用此特性 进行多条消息的推送 把连接挂起。。 asyncContext.complete(); } }); //此时之类 request的线程连接已经释放了 log.info("线程:" + Thread.currentThread().getName()); } }

注意:异步请求时,可以利用ThreadPoolExecutor自定义个线程池。

  • 1.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。大家可自己试试。
2018-08-15 23:03:04.082 INFO 6732 --- [nio-8080-exec-1] c.l.l.s.controller.ServletController : 线程:blog.csdn.net/paincupid/article/details/52266905
  • docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-async
  • www.importnew.com/29849.html
  • blog.lqdev.cn/2018/08/16/springboot/chapter-twenty/
  • 总结

    本章节主要是讲解了异步请求的使用及相关配置,如超时,异常等处理。设置异步请求时,记得不要忘记设置超时时间。**异步请求只是提高了服务的吞吐量,提高单位时间内处理的请求数,并不会加快处理效率的,这点需要注意。**。

    本文共计1568个文字,预计阅读时间需要7分钟。

    SpringBoot中如何实现异步请求的异步开发?

    Servlet 3.0之前,Servlet使用线程池处理每个请求,每个HTTP请求都由一个线程从头到尾负责处理。如果请求需要执行I/O操作,例如访问数据库或调用第三方服务,处理过程可能会受到影响。

    何为异步请求

    在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。其请求流程大致为:

    而在Servlet3.0发布后,提供了一个新特性:异步处理请求。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。其请求流程为:

    SpringBoot中如何实现异步请求的异步开发?

    在Servlet 3.0后,我们可以从HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。


    多说几句:

    随着Spring5发布,提供了一个响应式Web框架:Spring WebFlux。之后可能就不需要Servlet容器的支持了。以下是其先后对比图:

    左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。

    对于其发展前景还是拭目以待吧。有时间也该去了解下Spring5了。


    原生异步请求API说明

    在编写实际代码之前,我们来了解下一些关于异步请求的api的调用说明。

    • 获取AsyncContext:根据HttpServletRequest对象获取。
    AsyncContext asyncContext = request.startAsync();
    • 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理

    其监听器的接口代码:

    public interface AsyncListener extends EventListener { void onComplete(AsyncEvent event) throws IOException; void onTimeout(AsyncEvent event) throws IOException; void onError(AsyncEvent event) throws IOException; void onStartAsync(AsyncEvent event) throws IOException; }

    说明:

    • 1.onStartAsync:异步线程开始时调用
    • 2.onError:异步线程出错时调用
    • 3.onTimeout:异步线程执行超时调用
    • 4.onComplete:异步执行完毕时调用

    一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。

     

    需要注意的是只有在调用request.startAsync前将监听器添加到AsyncContext,监听器的onStartAsync方法才会起作用,而调用startAsync前AsyncContext还不存在,所以第一次调用startAsync是不会被监听器中的onStartAsync方法捕获的,只有在超时后又重新开始的情况下onStartAsync方法才会起作用。

    • 设置超时:通过setTimeout方法设置,单位:毫秒。

    一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。

    Servlet方式实现异步请求

    前面已经提到,可通过HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文。所以,我们来实际操作下。

    • 0.编写一个简单控制层
    /** * 使用servlet方式进行异步请求 * @author oKong * */ @Slf4j @RestController public class ServletController { @RequestMapping("/servlet/orig") public void todo(HttpServletRequest request, HttpServletResponse response) throws Exception { //这里来个休眠 Thread.sleep(100); response.getWriter().println("这是的请求返回"); } @RequestMapping("/servlet/async") public void todoAsync(HttpServletRequest request, HttpServletResponse response) { AsyncContext asyncContext = request.startAsync(); asyncContext.addListener(new AsyncListener() { @Override public void onTimeout(AsyncEvent event) throws IOException { log.info("超时了:"); //做一些超时后的相关操作 } @Override public void onStartAsync(AsyncEvent event) throws IOException { // TODO Auto-generated method stub log.info("线程开始"); } @Override public void onError(AsyncEvent event) throws IOException { log.info("发生错误:",event.getThrowable()); } @Override public void onComplete(AsyncEvent event) throws IOException { log.info("执行完成"); //这里可以做一些清理资源的操作 } }); //设置超时时间 asyncContext.setTimeout(200); //也可以不使用start 进行异步调用 // new Thread(new Runnable() { // // @Override // public void run() { // 编写业务逻辑 // // } // }).start(); asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(100); log.info("内部线程:" + Thread.currentThread().getName()); asyncContext.getResponse().setCharacterEncoding("utf-8"); asyncContext.getResponse().setContentType("text/html;charset=UTF-8"); asyncContext.getResponse().getWriter().println("这是的请求返回"); } catch (Exception e) { log.error("异常:",e); } //异步请求完成通知 //此时整个请求才完成 //其实可以利用此特性 进行多条消息的推送 把连接挂起。。 asyncContext.complete(); } }); //此时之类 request的线程连接已经释放了 log.info("线程:" + Thread.currentThread().getName()); } }

    注意:异步请求时,可以利用ThreadPoolExecutor自定义个线程池。

    • 1.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。大家可自己试试。
    2018-08-15 23:03:04.082 INFO 6732 --- [nio-8080-exec-1] c.l.l.s.controller.ServletController : 线程:blog.csdn.net/paincupid/article/details/52266905
  • docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-async
  • www.importnew.com/29849.html
  • blog.lqdev.cn/2018/08/16/springboot/chapter-twenty/
  • 总结

    本章节主要是讲解了异步请求的使用及相关配置,如超时,异常等处理。设置异步请求时,记得不要忘记设置超时时间。**异步请求只是提高了服务的吞吐量,提高单位时间内处理的请求数,并不会加快处理效率的,这点需要注意。**。