如何在SpringBoot中异步请求和异步调用

news2024/11/29 10:47:28

一、SpringBoot中异步请求的使用

1、异步请求与同步请求

特点:

可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。

一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

2、异步请求的实现

方式一:Servlet方式实现异步请求

  @RequestMapping(value = "/email/servletReq", method = GET)  public void servletReq (HttpServletRequest request, HttpServletResponse response) {      AsyncContext asyncContext = request.startAsync();      //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理      asyncContext.addListener(new AsyncListener() {          @Override          public void onTimeout(AsyncEvent event) throws IOException {              System.out.println("超时了...");              //做一些超时后的相关操作...          }          @Override          public void onStartAsync(AsyncEvent event) throws IOException {              System.out.println("线程开始");          }          @Override          public void onError(AsyncEvent event) throws IOException {              System.out.println("发生错误:"+event.getThrowable());          }          @Override          public void onComplete(AsyncEvent event) throws IOException {              System.out.println("执行完成");              //这里可以做一些清理资源的操作...          }      });      //设置超时时间      asyncContext.setTimeout(20000);      asyncContext.start(new Runnable() {          @Override          public void run() {              try {                  Thread.sleep(10000);                  System.out.println("内部线程:" + Thread.currentThread().getName());                  asyncContext.getResponse().setCharacterEncoding("utf-8");                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");                  asyncContext.getResponse().getWriter().println("这是异步的请求返回");              } catch (Exception e) {                  System.out.println("异常:"+e);              }              //异步请求完成通知              //此时整个请求才完成              asyncContext.complete();          }      });      //此时之类 request的线程连接已经释放了      System.out.println("主线程:" + Thread.currentThread().getName());  }

方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

  @RequestMapping(value = "/email/callableReq", method = GET)  @ResponseBody  public Callable<String> callableReq () {      System.out.println("外部线程:" + Thread.currentThread().getName());
      return new Callable<String>() {
          @Override          public String call() throws Exception {              Thread.sleep(10000);              System.out.println("内部线程:" + Thread.currentThread().getName());              return "callable!";          }      };  }
  @Configuration  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {
  @Resource  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;
  @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();  }}

方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理

    @RequestMapping(value = "/email/webAsyncReq", method = GET)    @ResponseBody    public WebAsyncTask<String> webAsyncReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        Callable<String> result = () -> {            System.out.println("内部线程开始:" + Thread.currentThread().getName());            try {                TimeUnit.SECONDS.sleep(4);            } catch (Exception e) {                // TODO: handle exception            }            logger.info("副线程返回");            System.out.println("内部线程返回:" + Thread.currentThread().getName());            return "success";        };        WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);        wat.onTimeout(new Callable<String>() {
            @Override            public String call() throws Exception {                // TODO Auto-generated method stub                return "超时";            }        });        return wat;    }

方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

@RequestMapping(value = "/email/deferredResultReq", method = GET)    @ResponseBody    public DeferredResult<String> deferredResultReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        //设置超时时间        DeferredResult<String> result = new DeferredResult<String>(60*1000L);        //处理超时事件 采用委托机制        result.onTimeout(new Runnable() {
            @Override            public void run() {                System.out.println("DeferredResult超时");                result.setResult("超时了!");            }        });        result.onCompletion(new Runnable() {
            @Override            public void run() {                //完成后                System.out.println("调用完成");            }        });        myThreadPoolTaskExecutor.execute(new Runnable() {
            @Override            public void run() {                //处理业务逻辑                System.out.println("内部线程:" + Thread.currentThread().getName());                //返回结果                result.setResult("DeferredResult!!");            }        });       return result;    }

二、SpringBoot中异步调用的使用

1、介绍

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

2、使用方式(基于spring下)

需要在启动类加入@EnableAsync使异步调用@Async注解生效

在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池

代码略。。。就俩标签,自己试一把就可以了

3、注意事项

在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。

4、什么情况下会导致@Async异步方法会失效?

  • a.调用同一个类下注有@Async异步方法:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。

  • b.调用的是静态(static )方法

  • c.调用(private)私有化方法

5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

@Controller@RequestMapping("/app")public class EmailController {
    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下    @Autowired    private ApplicationContext applicationContext;
    @RequestMapping(value = "/email/asyncCall", method = GET)    @ResponseBody    public Map<String, Object> asyncCall () {        Map<String, Object> resMap = new HashMap<String, Object>();        try{            //这样调用同类下的异步方法是不起作用的            //this.testAsyncTask();            //通过上下文获取自己的代理对象调用异步方法            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);            emailController.testAsyncTask();            resMap.put("code",200);        }catch (Exception e) {            resMap.put("code",400);            logger.error("error!",e);        }        return resMap;    }
    //注意一定是public,且是非static方法    @Async    public void testAsyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }
}

6、开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。

首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。

代码实现,如下:

@Service@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)public class EmailService {
    @Autowired    private ApplicationContext applicationContext;
    @Async    public void testSyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("异步任务执行完成!");    }

    public void asyncCallTwo() throws InterruptedException {        //this.testSyncTask();//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);//        emailService.testSyncTask();        boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;        //以下才是重点!!!        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);        EmailService proxy = (EmailService) AopContext.currentProxy();        System.out.println(emailService == proxy ? true : false);        proxy.testSyncTask();        System.out.println("end!!!");    }}

三、异步请求与异步调用的区别

两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python运维之远程登录paramiko(VsCode)

Python运维之远程登录----paramiko paramiko是一个用于做远程控制的模块&#xff0c;使用该模块可以对远程服务器进行命令或文件操作,paramiko是用python语言写的一个模块&#xff0c;遵循SSH2协议&#xff0c;支持以加密和认证的方式&#xff0c;进行远程服务器的连接。 par…

CentOS7中搭建docker

一、配置要求 系统&#xff1a;Centos7 Linux 内核&#xff1a;官方建议 3.10 以上 1.查看当前的内核版本 uname -r 2.卸载旧版本&#xff08;如果之前安装过的话&#xff09; yum remove docker docker-common docker-selinux docker-engine 二、安装Docker 1.安装依赖包 …

【C进阶】第十篇——数据在内存中的存储

数据类型的介绍 类型的基本归类 整型在内存中的存储 原码,反码,补码 大小端介绍 什么是大小端 为什么有大端和小端? 判断当前机器的字节序 浮点型在内存中的存储 例题引入 浮点数的存 浮点数的取 浮点数的比较 数据类型的介绍 char //字符数据类型 short…

现代制造技术产品设计与柔性制造系统的预测和分析

产品设计要求的市场竞争是面向市场&#xff0c;以用户为中心。精益设计的工业设计方法&#xff0c;以及一系列新的设计概念&#xff0c;如制造、装配过程、检查和测量、环境中的绿色设计等&#xff0c;使设计与柔性制造系统的整个过程紧密结合&#xff0c;包括从产品概念设计到…

Flask全栈开发教程

Flask全栈开发教程 成为使用 Flask、Python、HTML、CSS 和 MongoDB 的全栈 Web 开发人员&#xff01; 课程英文名&#xff1a;Web Developer Bootcamp with Flask and Python 此视频教程共5.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码附件全…

[前端]白屏性能优化

[前端]白屏性能优化 业务面会问的东西 从打开一个页面&#xff0c;到页面的画面展示经历了怎样的过程&#xff1f; 简单来说&#xff0c;有以下几个主要步骤。 1、URL解析&#xff1a;判断浏览器输入的是搜索内容还是URL&#xff1b; 2、查找缓存&#xff1a;如果能找到缓存…

笔试强训(四十四)

目录一、选择题二、编程题2.1 驼峰命名法2.1.1 题目2.1.2 题解2.2 单词倒排2.2.2 题解一、选择题 &#xff08;1&#xff09;IPv4版本的因特网总共有多少有效A类地址网络&#xff08;D&#xff09; A.255 B.128 C.256 D.126 A类地址的网络号从0~127共128个&#xff0c;其中有两…

大数据 常用命令

常用shell命令 管道命令 查看/etc目录信息前5行信息 执行命令&#xff1a;ll /etc | head -5 查看/etc/profile文件后5行信息 执行命令&#xff1a;cat /etc/profile | tail -5 grep命令 抓取/etc目录下的python信息 执行命令&#xff1a;ll /etc | grep python 抓…

解决visual studio对不安全函数的警告

解决visual studio 对scanf &#xff0c;strcpy&#xff0c;strcmp等函数的不安全警告报错 可以看到&#xff0c;编译器对scanf进行了报错&#xff0c;原因是说它不安全 编译器自己给了一种解决方案&#xff1a; 使用vs自带的 scanf_s&#xff0c;但是用这个函数&#xff0c;仅…

留学Assignment写作格式简单讲解

对于Assignment写作&#xff0c;不知道大家认为它最基础的东西是什么呢&#xff1f;可能大家的答案都会是格式&#xff0c;毕竟Assignment写作&#xff0c;最需要保证的就是格式无误&#xff0c;特别是文献综述的格式&#xff0c;错了的话后果是非常严重的&#xff0c;下面就给…

Springboot 那年我双手插兜,手写一个excel导出

前言 其实就是利用了csv 和txt 文件转换 。 不多说&#xff0c;开始玩代码。 正文 本篇内容&#xff1a; ① 了解根本生成excel内容的CSV文件玩法 ② 手动拼接文本演示 ③ 项目内实战写法&#xff0c;从数据库到导出 ④ 解决list数据过多&#xff0c;使用分批分页处理生成c…

极智AI | centos7源码编译tensorflow

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多笔记分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍一下 centos7 源码编译 tensorflow 的方法。 之前这篇《极智开发 | centos7源码编译bazel》已经为这篇 tensorflow 的源码编译铺平了道路&#xff0c;所以…

[附源码]Nodejs计算机毕业设计基于web的小说浏览系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

【Docker】Docker安装MySQL,并解决中文乱码和配置数据备份同步到宿主机

专栏精选文章 《Docker是什么&#xff1f;Docker从介绍到Linux安装图文详细教程》《30条Docker常用命令图文举例总结》《Docker如何构建自己的镜像&#xff1f;从镜像构建到推送远程镜像仓库图文教程》《Docker多个容器和宿主机之间如何进行数据同步和数据共享&#xff1f;容器…

银河麒麟操作系统V10SP1创建网页快捷方式至桌面的方法

修改浏览器配置文件添加快捷方式 1.在桌面点击鼠标右键&#xff0c;选择‘’打开终端‘’&#xff0c;终端界面显示‘桌面’ 2.在终端界面输入命令行 sudo vim qaxbrowser-safe.desktop (奇安信浏览 器的快捷方式) 进去后按‘/’然后输入‘Exec’&#xff0c;最后按回车键。…

Android监听UEvent之UEventObserver分析

&#xff08;1&#xff09;背景概述 众所周知&#xff0c;在安卓系统中有状态栏&#xff0c;在插入外设的时候&#xff0c;会在顶部状态栏显示小图标。 比如&#xff0c;camera设备&#xff0c;耳机设备&#xff0c;U盘&#xff0c;以及电池等等。这些都需要在状态栏动态显示。…

wy的leetcode刷题记录_Day58

wy的leetcode刷题记录_Day58 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2022-12-2 前言 力扣每日一题简单模拟左右抵消和二叉平衡搜索树 1769. 移动所有球到每个盒子所需的最小操作数和108. 将有序数组转换为二叉搜索树 目录wy的l…

无线路由器首次配置、修改WiFi名称和密码—— Cisco实验/家里实验

一、Csico实验 192.168.0.1、192.168.1.0和192.168.1.1是路由器常用的默认IP 1. 在PC打开浏览器&#xff08;PC用网线直连无线路由器&#xff09;&#xff0c; 输入无线路由器在局域网内的静态IP&#xff1a;192.168.0.1 2. 输入管理者的账号和密码&#xff0c;默认都是admin…

搜索与图论- Dijkstra 算法

文章目录一、Dijkstra 算法1. 简介2. 基本思想3. 朴素 Dijkstra 算法&#xff08;重点&#xff09;3.1 朴素 Dijkstra 算法实现步骤3.2 朴素 Dijkstra 算法伪代码4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。5. 堆优化朴素 Dijkstra 算法6. 堆优化 Dijkstra …

cookie、sessionStorage和localStorage的区别(二)

cookie、sessionStorage和localStorage的区别&#xff08;一&#xff09;详细精炼知识调用前言引入核心干货webstorage本地存储cookiesessionStoragelocalStorage知识调用 文章中可能用到的知识点前端学习&#xff1a;浏览器缓存方式有哪些&#xff08; cookie localstorage s…