专栏名称: 武哥聊编程
这里有技术,有段子,有生活,也有资源,要不然怎么叫 “私房菜” 呢?
目录
相关文章推荐
中国航务周刊  ·  【货主论坛】“2025跨境冷链与国际贸易协同 ... ·  17 小时前  
中国航务周刊  ·  全球第六大海运货代,收购百年物流企业 ·  昨天  
中国航务周刊  ·  这一港口春节期间创下47项生产高效纪录 ·  2 天前  
中国航务周刊  ·  不到24小时,美国邮政宣布:恢复接收中国内地 ... ·  2 天前  
51好读  ›  专栏  ›  武哥聊编程

Spring Boot 异步请求和异步调用

武哥聊编程  · 公众号  ·  · 2020-03-23 09:38

正文

今天跟大家分享Spring Boot 异步请求和异步调用 知识。

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

2 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!"






请到「今天看啥」查看全文