Spring Boot中异步请求和异步调用

一、SpringBoot中异步请求的使用

1、异步请求与同步请求

Spring Boot中异步请求和异步调用

Spring Boot中异步请求和异步调用
特点:

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

一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过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类来设置默认线程池和超时处理。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.ispace.com/spring-boot-learning-2x/Java项目分享

@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可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。Java项目分享

@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;
}

二、SpringBoot中异步调用的使用

1、介绍

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。

比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.space.com/spring-boot-learning-2x/

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

  • 需要在启动类加入 @EnableAsync使异步调用 @Async注解生效
  • 在需要异步执行的方法上加入此注解即可 @Async("threadPool"),threadPool为自定义线程池
  • 代码略。。。就俩标签,自己试一把就可以了

3、注意事项

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

调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如 @Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。Java项目分享

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

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

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

3.调用(private)私有化方法

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

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

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

@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("异步任务执行完成!");
    }

}

3.开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.ispace.com/spring-boot-learning-2x/

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

四、总结

异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。

Original: https://www.cnblogs.com/JimmyThomas/p/16029631.html
Author: JimmyThomas
Title: Spring Boot中异步请求和异步调用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612285/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Dubbo源码(七)-集群

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 集群(cluster)就是一组计算机,它们作为一个总体向用户提供一组网络资源…

    数据库 2023年6月11日
    0142
  • entitybuilder–一个简单的业务通用框架

    关于业务通用框架的思考 业务系统是千差万别的,例如,保存、更新和删除订单,或者保存订单和保存客户,走的根本不是一个流程。但是,它们还是有共同点,它们的流程大致可以分成下面的几个部分…

    数据库 2023年6月6日
    0124
  • [springmvc]ajax异步请求数据详细简单

    10.Ajax异步请求 Ajax即 Asynchronous Javascript And XML(异步JavaScript和XML在 2005年被Jesse James Garr…

    数据库 2023年6月16日
    0112
  • jspdf.js+html2canvas将HTMl导出PDF

    jspdf.js+html2canvas将HTMl导出PDF 功能: PDF分页插入页头页尾 输出A4格式PDF 支持单页、多页输出 效果预览:查看演示PDFdemo地址:demo…

    数据库 2023年6月11日
    0147
  • 配置中心Nacos(服务发现)

    服务演变之路 单体应用架构 在刚开始的时候,企业的用户量、数据量规模都⽐较⼩,项⽬所有的功能模块都放在⼀个⼯程中编码、编译、打包并且部署在⼀个Tomcat容器中的架构模式就是单体应…

    数据库 2023年6月9日
    0152
  • Tomcat的安装和使用

    一·Tomcat 安装 1、Toncat下载 通过访问Tomcat官方网站下载Tomcat文件。 Tomcat提供了压缩版和安装版,还区分 32 位和 64 位系统版。下载的时候注…

    数据库 2023年6月11日
    0128
  • mybatis-plus详细讲解

    本文笔记都是观看狂神老师视频手敲的,视频地址:https://www.bilibili.com/video/BV17E411N7KN 学java后端的都可以去看一下,从基础到架构很…

    数据库 2023年6月14日
    0132
  • VMware下的centOS安装与异常记录

    VMware下的centOS安装与异常记录 随笔 记录在使用虚拟机安装centOs的过程中遇到的一些坑,记录一下,之前发在C**N上的,现在决定在这里重新整理一下,加上一些细节的补…

    数据库 2023年6月6日
    0154
  • BigDecimal 设置小数位数、小数比例转换整数

    控制小数位数 DecimalFormat decimalFormat = new DecimalFormat("0.00"); decimalFormat.fo…

    数据库 2023年6月6日
    0122
  • 简单的2021年终总结

    当大家开开心心跨年的时候,我在补年终总结。 小时候恨不得时间过得快一点,现在不这么想了。 我的 2021年,都是平静、反复的一天天,没有出书、没有开源、没有跳槽、没有升官。没错,这…

    数据库 2023年6月6日
    0116
  • Html转换PDF(Java实用版)

    前言: 在工作当中,遇到了需要把HTML页面转化为PDF文档,有很多中实现,如下进行一个对比,大家个借鉴去进行使用 各实现对比表 于Windows平台进行测试: 此博客仅基于ITe…

    数据库 2023年6月16日
    0153
  • 观点 | NoSQL 产品的 SaaS 化之路

    王奇 顾问软件工程师目前从事 PaaS 中间件服务(Redis / MongoDB / ELK 等)开发工作,对 NoSQL 数据库有深入的研究以及丰富的二次开发经验,热衷对 No…

    数据库 2023年5月24日
    0124
  • 「萌新指南」SOA vs. 微服务:What’s the Difference?

    实话实说,在我还没有实习之前,我是连 SOA 是啥都不知道的,只听说过微服务,毕竟微服务实在太火了,想不知道都难,我觉得实习的时候肯定也是微服务,进组之后发现是 SOA 架构,当时…

    数据库 2023年6月6日
    0130
  • 7、定时进行数据批处理任务

    一、StopWatch时间控制类: StopWatch 是spring工具包org.springframework.util下的一个工具类,主要用于计算同步 单线程执行时间。 1、…

    数据库 2023年6月6日
    0140
  • asyncio 异步编程

    首先了解一下协程,协程的本质就是一条线程,多个任务在一条线程上来回切换,协程的所有切换都是基于用户,只有在用户级别才能感知到的 IO 才会用协程模块来规避,在 python 中主要…

    数据库 2023年6月9日
    096
  • Linux网络配置

    Linux网络配置 NAT网络配置 查看网络IP和网关 可以在 编辑->虚拟网络编辑器中 查看网络IP和网关 说明:1.什么是IP协议/地址?即”网络之间能相互连…

    数据库 2023年6月16日
    0131
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球