Spring Boot 异步请求和异步调用,一文搞定
一、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<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;
}
二、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<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("异步任务执行完成!");
}
}开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。代码实现,如下:
开启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的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~
Spring Boot 异步请求和异步调用,一文搞定的更多相关文章
- Spring Data Redis 详解及实战一文搞定
SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能.它提供了与商店互动的低级别和高级别抽象,使用户免受 ...
- SpringBoot中异步请求和异步调用(看这一篇就够了)
原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!! 一.SpringBoot中异步请求的使用 ...
- java 调用 C# 类库搞定,三步即可,可以调用任何类及方法,很简单,非常爽啊
java 调用 C# 类库搞定,三步即可,可以调用任何类及方法,很简单,非常爽啊 java 调用 C# 类库搞定,可以调用任何类及方法,很简单,非常爽啊 总体分三步走: 一.准备一个 C# 类库 (d ...
- Spring Boot使用@Async实现异步调用
原文:http://blog.csdn.net/a286352250/article/details/53157822 项目GitHub地址 : https://github.com/FrameRes ...
- Spring boot 使用WebAsyncTask处理异步任务
上文介绍了基于 @Async 注解的 异步调用编程,本文将继续引入 Spring Boot 的 WebAsyncTask 进行更灵活异步任务处理,包括 异步回调,超时处理 和 异常处理. 正文 1. ...
- Spring boot 实现高吞吐量异步处理(适用于高并发场景)
技术要点 org.springframework.web.context.request.async.DeferredResult<T> 示例如下: 1. 新建Maven项目 asy ...
- Spring Boot任务(定时,异步,邮件)
一.定时任务 开启定时任务(在Spring Boot项目主程序上添加如下注解) @EnableScheduling //开启定时任务的注解 创建定时任务(创建一个Service如下) @Service ...
- spring boot:使用log4j2做异步日志打印(spring boot 2.3.1)
一,为什么要使用log4j2? log4j2是log4j的升级版, 升级后更有优势: 性能更强/吞吐量大/支持异步 功能扩展/支持插件/支持自定义级别等 这些优 ...
- spring boot 并发请求,其他系统接口,丢失request的header信息【多线程、线程池、@Async 】
场景:一次迭代在灰度环境发版时,测试反馈说我开发的那个功能,查询接口有部分字段数据是空的,后续排查日志,发现日志如下: feign.RetryableException: cannot retry d ...
随机推荐
- SSM整合大体步骤
SSM整合步骤: 1. 导入jar spring: springMVC: mybatis: 第三方支持:log4j,pageHelper,AspectJ,jackson,jstl 2. 搭建sprin ...
- 第二章 FreeBSD之开机关机命令
立即关机,但是不关闭电源 # shutdown -h now 立即关机,并且关闭电源 # shutdown -p now 重启命令 # shutdown -r now
- C# 多线程技术
这节讲一下多线程(Thread)技术. 在讲线程之前,先区分一下程序,进程,线程三者的区别,大体上说,一个程序可以分为多个进程,一个进程至少由一个线程去执行,它们是层层包含的关系.我们写的程序,就是一 ...
- Unity API笔记
1. 返回某范围的随机数 Random.Range method. static float Range(float min, float max); returns a float – return ...
- win10下卸载ubuntu的合理操作
这里不推荐使用第三方软件,因为可能会被植入病毒,而且windows自带的命令行工具足以完成任务! win10系统自带的一个命令行工具--diskpart 在cmd中输入"diskpart&q ...
- 在ZOHO企业网盘中如何快速搜索文件?
现在越来越多的企业采用企业网盘来存储文档和资料,而且现在市面上的企业网盘各种各样.在使用企业网盘过程中,很多用户会问到企业网盘中如何快速搜索文件的问题.但是无论是"标签"功能还是普 ...
- 解析CentOS 8上的Xrdp服务器安装
解析CentOS 8上的Xrdp服务器安装 Linux系统技术交流QQ群(915246)验证问题答案:刘遄 导读 Xrdp 是 Microsoft 远程桌面协议 (RDP) 的开源实现,允许您以图 ...
- Linux 文件隐藏属性-创建文件默认权限
Linux特殊权限介绍 # password原本只有root可以访问但是为什么普通用户在修改密码的时候也可以改里面的内容. [root@gong ~]# ll /usr/bin/passwd -rws ...
- IDEA 创建 Maven 项目每次都需要重新配置问题
问题描述 通过 File->Settings 设置 maven 配置,在 IDEA 新创建 Maven 项目时设置的 maven 配置会被重置,导致每次创建新 Maven 项目都需要重新设置一遍 ...
- 阿里云轻量服务器价格及轻量与ECS服务器区别比较
https://yq.aliyun.com/articles/221647 摘要: 阿里云轻量应用服务器价格表及介绍,关于轻量应用服务器和ECS服务器的性能对比 阿里云轻量应用服务器是阿里云新推出的服 ...