详解SpringBoot中异步请求和异步调⽤(看完这⼀篇就够
了)
⼀、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 {spring启动流程面试回答
System.out.println("发⽣错误:"+Throwable());
}
@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());
} catch (Exception e) {
System.out.println("异常:"+e);
}
//异步请求完成通知
//此时整个请求才完成
asyncContextplete();
}
});
//此时之类 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);
}
@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);
@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);
//处理超时事件采⽤委托机制
@Override
public void run() {
System.out.println("DeferredResult超时");
result.setResult("超时了!");
}
});
@Override
public void run() {
//完成后
System.out.println("调⽤完成");
}
});
@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异步⽅法会失效?
调⽤同⼀个类下注有@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{
//这样调⽤同类下的异步⽅法是不起作⽤的
//stAsyncTask();
//通过上下⽂获取⾃⼰的代理对象调⽤异步⽅法
EmailController emailController = (Bean(EmailController.class);
resMap.put("code",200);
}catch (Exception e) {
resMap.put("code",400);
<("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 {
//stSyncTask();
// EmailService emailService = (Bean(EmailService.class);
// stSyncTask();
boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;
boolean isCglib = AopUtils.isCglibProxy(EmailController.class); //是否是CGLIB⽅式的代理对象;
boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class); //是否是JDK动态代理⽅式的代理对象;
//以下才是重点
EmailService emailService = (Bean(EmailService.class);
EmailService proxy = (EmailService) AopContext.currentProxy();
System.out.println(emailService == proxy ? true : false);
System.out.println("end");
}
}
三、异步请求与异步调⽤的区别
两者的使⽤场景不同,异步请求⽤来解决并发请求对服务器造成的压⼒,从⽽提⾼对请求的吞吐量;⽽异步调⽤是⽤来做⼀些⾮主线流程且不需要实时计算和响应的任务,⽐如同步⽇志到kafka中做⽇志分析等。
异步请求是会⼀直等待response相应的,需要返回结果给客户端的;⽽异步调⽤我们往往会马上返回给客户端响应,完成这次整个的请求,⾄于异步调⽤的任务后台⾃⼰慢慢跑就⾏,客户端不会关⼼。
四、总结
异步请求和异步调⽤的使⽤到这⾥基本就差不多了,有问题还希望⼤家多多指出。
这边⽂章提到了动态代理,⽽spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多⽀持哈~
总结
以上所述是⼩编给⼤家介绍的SpringBoot中异步请求和异步调⽤问题,希望对⼤家有所帮助,如果⼤家有任何疑问请给我留⾔,⼩编会及时回复⼤家的。在此也⾮常感谢⼤家对⽹站的⽀持!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论