SpringBoot中异步请求和异步调用

阅读文本大概须要20分钟。php

1、SpringBoot中异步请求的使用

一、异步请求与同步请求

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

特色:html

能够先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,能够在耗时处理完成(例如长时间的运算)时再对客户端进行响应。一句话:增长了服务器对客户端请求的吞吐量(实际生产上咱们用的比较少,若是并发请求量很大的状况下,咱们会经过nginx把请求负载到集群服务的各个节点上来分摊请求压力,固然还能够经过消息队列来作请求的缓冲)。nginx

二、异步请求的实现

方式一: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;
    }
2、SpringBoot中异步调用的使用

一、介绍

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

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

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

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

代码略。就俩标签,本身试一把就能够了服务器

三、注意事项

在默认状况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,由于线程不重用,每次调用都会建立一个新的线程。可经过控制台日志输出能够看出,每次输出线程名都是递增的。因此最好咱们来自定义一个线程池。并发

调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来讲,由于Spring在启动扫描时会为其建立一个代理类,而同类调用时,仍是调用自己的代理类的,因此和日常调用是同样的。其余的注解如@Cache等也是同样的道理,说白了,就是Spring的代理机制形成的。因此在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。app

四、什么状况下会致使@Async异步方法会失效?

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

调用的是静态(static )方法async

调用(private)私有化方法

五、解决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)注解。代码实现,以下:

@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!!!");
    }
}

 

3、异步请求与异步调用的区别

二者的使用场景不一样,异步请求用来解决并发请求对服务器形成的压力,从而提升对请求的吞吐量;而异步调用是用来作一些非主线流程且不须要实时计算和响应的任务,好比同步日志到kafka中作日志分析等。

异步请求是会一直等待response相应的,须要返回结果给客户端的;而异步调用咱们每每会立刻返回给客户端响应,完成此次整个的请求,至于异步调用的任务后台本身慢慢跑就行,客户端不会关心。

4、总结

异步请求和异步调用的使用到这里基本就差很少了,有问题还但愿你们多多指出。

这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理作详细解读,还望多多支持哈~

往期精彩

 关注我

天天进步一点点

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

相关文章
相关标签/搜索