一、Spring Boot中异步请求的使用

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 callableReq () {      System.out.println("外部线程:" + Thread.currentThread().getName());      return new Callable() {          @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 webAsyncReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        Callable 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 wat = new WebAsyncTask(3000L, result);        wat.onTimeout(new Callable() {            @Override            public String call() throws Exception {                // TODO Auto-generated method stub                return "超时";            }        });        return wat;    }

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

@RequestMapping(value = "/email/deferredResultReq", method = GET)    @ResponseBody    public DeferredResult deferredResultReq () {        System.out.println("外部线程:" + Thread.currentThread().getName());        //设置超时时间        DeferredResult result = new DeferredResult(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;    }

二、Spring Boot中异步调用的使用

1、介绍

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

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

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

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

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

3、注意事项

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

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

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

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

调用同一个类下注有@Async异步方法:

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

调用的是静态(static )方法

调用(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 asyncCall () {        Map resMap = new HashMap();        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("异步任务执行完成!");    }}

开启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相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

四、总结

异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~

springboot服务调用超时_Spring Boot 异步请求和异步调用,一文搞定相关推荐

  1. Spring Boot 异步请求和异步调用

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.Spring Boot中异步请求的使用 1.异步请求与同步请求 ...

  2. Spring Boot 异步请求和异步调用,一文搞定!

    一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...

  3. 面试官 | SpringBoot 中如何实现异步请求和异步调用?

    作者 | 会炼钢的小白龙 来源 | cnblogs.com/baixianlong/p/10661591.html 一.SpringBoot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放 ...

  4. vue一个页面发出多个异步请求_Vue异步请求最佳实践

    一.当前存在的问题 目前项目前端请求后台数据的方式是这样的: 页面中method中dispatch到action action调用mutation,请求axios 请求到数据后存储到state中 页面 ...

  5. http请求与响应,同步异步请求以及异步请求axios的配置

    文章目录 http http简介,协议 http请求 http响应 接收请求行请求头数据 同步异步请求 异步请求axios的配置 配置文件 http http简介,协议 http是超文本传输协议 (H ...

  6. (八)JS异步进阶,更深更广搞定JS异步【想要进大厂,更多异步的问题等着你】

    JS异步进阶 提问 event loop JS如何执行 示例 总结event loop的过程 DOM事件和event loop promise进阶 三种状态 状态的表现和变化 then和catch对状 ...

  7. restful post请求_猿蜕变9——一文搞定SpringMVC的RESTFul套路

    看过之前的蜕变系列文章,相信你对springMVC有了一定的认识.对springMVC的Interceptor拦截器,也有了一定的认识.今天我们来开启新讨论,讲一讲springMVC对那一种休闲风的支 ...

  8. springboot 事务统一配置_Spring Boot实现分布式微服务开发实战系列(五)

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  9. springboot 订单重复提交_Spring Boot (一) 校验表单重复提交

    一.前言 在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据. 存在如上问题可以交给前端解决,判断多长时间内不能再次点 ...

最新文章

  1. Guice系列之用户指南(七)
  2. 微信小程序之后台https域名绑定以及免费的https证书申请
  3. 扩增子三部曲:2分析流程(共7节万字)
  4. 这么小的key-val数据库居然也支持事务——与短跑名将同名的数据库Bolt
  5. 人工智能数学基础----导数
  6. linux rs232触摸屏驱动程序,Linux下的触摸屏驱动
  7. php代码以什么开始以什么结束,【后端开发】php语句以什么符号结束
  8. 物体运动到一个点停止_大颗粒搭建中常见的结构运动
  9. oracle学习资料
  10. win7连接共享打印机时安装驱动程序出现0x00000bcb错误
  11. Android音视频开发全系列教程
  12. 数据原理——2、ChIA-PET
  13. oracle坏块,教你手把手修复oracle坏块(一)!!!
  14. 完美识别率 迅捷PDF转换成Word转换器发布
  15. 适用于Webstorm的25个最佳Javascript插件
  16. java实时语音通话案例_java语音播报案例
  17. php生成sn码,php生成唯一随机码
  18. BiLSTM / BiRNN / BiLSTM-CRF / Bert-BiLSTM-CRF 全网最强大厂面试级深度的知识点整理
  19. 计算机搜索栏无法搜索怎么办,电脑桌面搜索框无法使用失效怎么办?
  20. BUU-pwn(一)

热门文章

  1. ansys怎么使用anand模型_【干货】经典ANSYS 与 Workbench如何实现联合仿真,相互切换操作。...
  2. tomcat 如何跳转到apache_第二十期:基于tomcat部署jforum站点,并结合nginx实现动静分离...
  3. 快速傅立叶变换(FFT)算法(原来这就是蝶形变换)
  4. 中职计算机专业选修课程,《办好中职学校计算机专业的几点思考.doc
  5. python 将数据写入csv文件
  6. 软件测试实践报告文档,软件测试实践报告.doc
  7. 改变你一生命运的话语 不得不信
  8. Sql语句中两个比较迷糊的概念:“连接查询” 与 “外键约束”
  9. Nand Flash数据存储单元的整体架构
  10. 【转】Django 数据库的操作