精华内容
下载资源
问答
  • 在搜集资料时发现一篇文章《SpringBoot中异步请求和异步调用》,以及许多它的衍生文章。文章的确不错,但文章中提到的“异步请求和异步调用”这两个概念,一直百思不得其解,衍生文章中也是在混着用。 终于忍不住想...

    当你打开本文,首先想说的是,这篇文章不仅仅是在讲一个知识点,更重要的是在讲一种学习方法:质疑与求证。

    前言

    最近在研究Spring Boot的异步处理,准备写一篇文章。在搜集资料时发现一篇文章《SpringBoot中异步请求和异步调用》,以及许多它的衍生文章。文章的确不错,但文章中提到的“异步请求和异步调用”这两个概念,一直百思不得其解,衍生文章中也是在混着用。

    终于忍不住想探究一番,发现除了那篇文章和衍生的文章之外,并没有其他证据来区别这两个概念。直到在Stack Overflow上搜到了一篇七年前的文章,这个疑惑才解开。

    今天就带大家一起探究一下“异步请求和异步调用”这两个概念。

    异步请求和异步调用的区别

    上面提到的文章中有这样两段话来讲异步请求和异步调用的区别:

    区别一:异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

    区别二:异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台运行就可以了,客户端不会关心。

    仔细阅读和分析之后,文章中提到的异步请求应该指的是通过Servlet的AsyncContext对象或Spring中的Callable、DeferredResult、WebAsyncTask等方式进行业务的异步处理。而文中异步调用指的是类似在Spring中调用@Async注解的异步方法。

    你看了上面的两个概念的区别,是否觉得很有道理?感觉已经成功区分了异步请求和异步调用了吗?

    那么,是否想过一个问题,我们通常说请求一个接口和调用一个接口时难道不是一个意思吗?怎么在这里就变成两个截然不同的概念了?

    疑问探究

    带着上面的疑问,开始用搜索引擎进行搜索。发现当搜索“异步请求和异步调用的区别”时,搜出来的内容基本上都来自上述文章及衍生文章,或者是讲异步和同步的区别。这并不能说明什么,但也说明了这两个概念的区别并没有得到普遍的认可。

    于是开始尝试通过英文搜索引擎进行搜索。先搜了“the difference between asynchronous call and asynchronous request”,没有想要的结果,大多数文章还是讲异步和同步的区别。

    改变关键字搜“the difference between asynchronous call and asynchronous method”,结果一样,并没有想要的文章。但却搜出了一个Stack Overflow上的关联问题“Synchronous Vs Asynchronous related to web services”。

    在该问题的下面出现了两个词组:Asynchronous service和Asynchronous calls。再仔细看两个词组的解释,发现正是想要的结果。

    异步调用与异步服务

    其实在英文搜索的过程中已经发现了,探讨异步与同步的区别时,经常会出现asynchronous request和asynchronous call这两个词组,而且它们都是在讲异步与同步的区别,而没有讲它们两个的区别。所以无论异步调用还是异步请求本质上是一回事。

    而《SpringBoot中异步请求和异步调用》一文中所说的异步请求和异步调用的区别,概念上也并没有说清楚什么是异步请求,什么是异步调用。而且只有那几篇文章这么讲,所以可以认为异步请求和异步调用是同一个概念(asynchronous call)。而作者所讲的“异步调用”指的应该异步服务(Asynchronous service)。

    为了进一步证明上面的结论,下面将问答中对两个概念的解释简单翻译一下。

    Asynchronous service和Asynchronous calls

    Asynchronous service

    当你的web服务执行需要耗费大量时间,比如从文件系统中读取一个大文件。此时,如果你使用“同步”的web服务,那么客户端必须等待处理完成,通常会阻塞线程,对于高并发的服务,会出现性能问题。

    如果将web服务设置为异步服务,那么可以把耗时服务委托给另外一个线程,或者使用非阻塞机制,在未来的某刻进行返回。还以读取大文件为例,可以使用异步的ReadFile方法来读取大文件。这样便能避免线程阻塞,提高吞吐量。

    Asynchronous calls

    值得注意的是:你可以通过异步的方式调用一个非异步的的服务。比如在JavaScript中的Ajax的调用:

    var jqxhr = $.ajax( "AnyService.svc" )
        .done(function() { alert("success"); })
        .fail(function() { alert("error"); })
        .always(function() { alert("complete"); });
    
    alert("Called");
    

    执行上述示例,首先展示“Called”,然后展示“success”,因为执行的过程中并不需要等到服务返回的结果。而被调用的服务也不需要是异步的。

    总之,服务实现的异步与同步特性完全独立于客户端调用的异步和同步特性。也就是说客户端可以异步的去调用同步服务,而且客户端也可以同步的去调用异步服务。

    小结

    通过上面的追踪分析,关于异步请求和异步调用我们可以理解为是一回事,同时我们还得知了所谓的客户端异步和同步与服务的异步与同步是相互独立的。也就是客户端可以异步请求也可以同步请求,服务可以异步处理也可以同步处理。两两组合可以有四种情况。

    另外,更重要的是我们要学会通过搜索中英文资料来解答自己心中的疑惑,而且英文资料相对更准确一些,所以首推英文。在探索疑惑问题的过程中往往还能有不少意外的收获。

    原文链接:《异步请求和异步调用有区别?直到看到了7年前的一个问答


    程序新视界

    公众号“ 程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

    微信公众号:程序新视界

    展开全文
  • Spring Boot 异步请求和异步调用

    千次阅读 2020-03-17 16:24:54
    1Spring Boot中异步请求的使用 1、异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的...

    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<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 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)注解。代码实现,如下:

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

    参考来源:https://cnblogs.com/baixianlong/p/10661591.html   
    展开全文
  • 主要介绍了SpringBoot中异步请求和异步调用问题,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
  •  二:异步调用  不会阻塞启动操作的调用线程,调用程序必须通过轮流检测,或者等待完成信号来发现调用的完成   三:同步调用WebService  同步调用WebService就是我们平常的调用的写法  1:AsynCall.asmx的...
  • 一、SpringBoot中异步请求的使用 1、异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间...

    原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!!

    一、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 {
                    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;
    }
    

    二、SpringBoot中异步调用的使用

    1、介绍

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

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

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

    3、注意事项

    • 在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。
    • 调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。

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

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

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

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

    2. 其实我们的注入对象都是从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("异步任务执行完成!");
          }
      
      }
      
    3. 开启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的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~

    个人博客地址:

    csdn:https://blog.csdn.net/tiantuo6513
    cnblogs:https://www.cnblogs.com/baixianlong
    segmentfault:https://segmentfault.com/u/baixianlong
    github:https://github.com/xianlongbai

    展开全文
  • 文章目录前言远程调用出现的问题及解决方案异步调用出现的问题及解决方案 前言 最近一直在梳理之前做过的项目,想到之前遇到过的一个问题,场景是这样的,在我提交订单时,需要查询用户的地址信息购物车被勾选的...

    前言

    最近一直在梳理之前做过的项目,想到之前遇到过的一个问题,场景是这样的,在我提交订单时,需要查询用户的地址信息和购物车被勾选的购物项,这样的话,我需要调用两个服务,一个是会员服务,一个是购物车服务。由于用户登陆信息是在整个系统共享的(这里采用分布式session解决),所以我在提交订单的时,调用购物车服务的时候,购物车服务的拦截器会拦截请求,判断用户是否登录。这时候请求头丢失,导致购物车服务拦截器返回用户未登录,但实际上是已经登录过的。还有一个问题是异步调用的时候,老请求线程不共享的问题,导致我业务中获取不到老请求报空指针异常的问题

    远程调用出现的问题及解决方案

    在分布式项目中,发送请求大致就两种,一种是浏览器访问,第二种是服务与服务之间通过OpenFeign远程调用。浏览器发送请求时,它会带上请求头的信息的,所以不会导致cookie丢失,这样用户真实登录的情况下不会判断未登录的异常情况。深入源码发现,Feign会重新创建一个request,这个请求是没有任何请求头的,这个请求模板会遍历请求拦截器的apply方法来丰富这个请求模板
    在这里插入图片描述
    看到这个地方就有办法解决了,解决方案就是,我写了一个feign拦截器,这里面注入了一个RequestInterceptor的对象,它是一个接口,我重写了它的apply方法,在里面拿到老请求中的请求头信息,放到这个新的请求模板里,我这里更新的是cookie。来看下代码:

    @Configuration
    public class GuliFeignConfig {
    
        @Bean("requestInterceptor")
        public RequestInterceptor requestInterceptor() {
    
            RequestInterceptor requestInterceptor = new RequestInterceptor() {
                @Override
                public void apply(RequestTemplate template) {
                    //1、使用RequestContextHolder拿到刚进来的请求数据
                    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    
                    if (requestAttributes != null) {
                        //老请求
                        HttpServletRequest request = requestAttributes.getRequest();
    
                        if (request != null) {
                            //2、同步请求头的数据(主要是cookie)
                            //把老请求的cookie值放到新请求上来,进行一个同步
                            String cookie = request.getHeader("Cookie");
                            template.header("Cookie", cookie);
                        }
                    }
                }
            };
            return requestInterceptor;
        }
    }
    

    这样每次进行feign远程调用的时候都需要走到这里丰富一下请求模板,带上cookie,这样用户登录信息就能正常的判断已登录问题。
    这个问题就得到解决了。我画了个整体的流程图:
    在这里插入图片描述

    异步调用出现的问题及解决方案

    前面已经说了场景了,我这个service里有两个查询任务,一个是去会员服务中查询会员的详细地址信息,一个是购物车服务中查询购物车勾选的购物项信息,这里我做了步优化,将这两个任务丢到我在这个模块下创建的线程池里,让其异步处理,这里用到CompletableFuture异步编排的功能,这样吞吐量就会有所提升。但是问题来了,在我获取老请求的时候,出现空指针的问题,我们来看看获取老请求是怎么获取的。
    在这里插入图片描述
    再深入源码看看,这个RequestContextHolder是怎么存储的请求数据

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
    @Nullable
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
        if (attributes == null) {
            attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
        }
    
        return attributes;
    }
    

    这个requestAttributesHolder其实就是个ThreadLocal,ThreadLocal是线程内部共享的,但是跨线程就是完全不同的数据了,我这里就是,当我将两个任务丢到线程池中创建其他的线程执行的话,它是拿不到我主线程的请求信息的,所以就导致了获取值为空的现象。
    我是这么来解决的,来看下我的代码

     /**
         * 订单确认页返回需要用的数据
         * @return
         */
        @Override
        public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    
            //构建OrderConfirmVo
            OrderConfirmVo confirmVo = new OrderConfirmVo();
    
            //获取当前用户登录的信息
            MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
    
            //TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    
            //开启第一个异步任务
            CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
    
                //每一个线程都来共享之前的请求数据
                RequestContextHolder.setRequestAttributes(requestAttributes);
    
                //1、远程查询所有的收获地址列表
                List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
                confirmVo.setMemberAddressVos(address);
            }, threadPoolExecutor);
    
            //开启第二个异步任务
            CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(() -> {
    
                //每一个线程都来共享之前的请求数据
                RequestContextHolder.setRequestAttributes(requestAttributes);
    
                //2、远程查询购物车所有选中的购物项
                List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
                confirmVo.setItems(currentCartItems);
                //feign在远程调用之前要构造请求,调用很多的拦截器
            }, threadPoolExecutor).thenRunAsync(() -> {
                List<OrderItemVo> items = confirmVo.getItems();
                //获取全部商品的id
                List<Long> skuIds = items.stream()
                        .map((itemVo -> itemVo.getSkuId()))
                        .collect(Collectors.toList());
    
                //远程查询商品库存信息
                R skuHasStock = wmsFeignService.getSkuHasStock(skuIds);
                List<SkuStockVo> skuStockVos = skuHasStock.getData("data", new TypeReference<List<SkuStockVo>>() {});
    
                if (skuStockVos != null && skuStockVos.size() > 0) {
                    //将skuStockVos集合转换为map
                    Map<Long, Boolean> skuHasStockMap = skuStockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                    confirmVo.setStocks(skuHasStockMap);
                }
            },threadPoolExecutor);
    
            //3、查询用户积分
            Integer integration = memberResponseVo.getIntegration();
            confirmVo.setIntegration(integration);
    
            //4、价格数据自动计算
    
            //TODO 5、防重令牌(防止表单重复提交)
            //为用户设置一个token,三十分钟过期时间(存在redis)
            String token = UUID.randomUUID().toString().replace("-", "");
            redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES);
            confirmVo.setOrderToken(token);
    
    
            CompletableFuture.allOf(addressFuture,cartInfoFuture).get();
    
            return confirmVo;
        }
    

    在这里插入图片描述
    每一个线程都来共享之前的这个请求数据。问题得到解决。来画张图来看一下这个整体的问题
    在这里插入图片描述

    展开全文
  • Vue中的同步调用和异步调用

    千次阅读 2020-03-03 11:26:54
    Promise实现异步调用 异步调用,增加a、b两个方法,并在mounted中调用。 观察客户端,并没有按照方法执行的顺序输出,使用Promise实现了异步调用。 观察客户端,并没有按照方法执行的顺序输出,使用Promise实现了...
  • 本文将主要通过“同步调用”、“异步调用”、“异步回调”三个示例来讲解在用委托执行同一个“加法类”的时候的的区别和利弊。  首先,通过代码定义一个委托和下面三个示例将要调用的方法:   public ...
  • 关于http 请求的同步与异步调用

    千次阅读 2018-09-12 13:33:46
    这里先说明我个人判断同步请求还是异步请求的依据:所调用的请求(某个url),阻塞了后续代码的执行则为同步,反之则为异步。 同步与异步的概念: 同步、异步常常用来描述一次方法调用过程。 同步:方法一旦调用,...
  • Java接口异步调用

    2020-08-26 00:11:43
    主要介绍了Java接口异步调用,下面我们来一起学习一下吧
  • Jquery Ajax学习实例6 向WebService发出请求,返回DataSet(XML) 异步调用实现代码,需要的朋友可以参考下。
  • 本文阐述在发出异步请求时所发生的情况,以及SCA运行时如何处理消息系统中的异步消息。了解如何开发中介处理程序来监视SCA异步消息,以及如何使用中介处理程序来分析异步调用。IBMWebSphereIntegrationDeveloper...
  • 同步调用和异步调用

    万次阅读 2017-02-10 14:02:34
    我们知道,常见的方法...但是,如果我们面对是基于粗粒度的服务组件,面对的是一些需要比较长时间才 能有响应的应用场景,那么我们就需要一种非阻塞式调用方式,即异步调用方式。 SCA编程模式提供了三种方式的异步
  • SpringBoot异步调用方法

    千次阅读 2019-04-23 18:28:29
    一、spring boot--使用异步请求,提高系统的吞吐量 https://blog.csdn.net/liuchuanhong1/article/details/78744138 1.使用Callable来实现 2. 通过WebAsyncTask,也能实现异步调用 这种方式上面的callable方式...
  • 实现WCF 同步、异步调用请求实例, Winform 实现客户端 服务端。
  • CAT中实现异步请求调用链查看

    万次阅读 多人点赞 2019-12-18 19:39:12
    对于异步请求API,因为不在同一线程中,在子线程中无法获取到父线程消息树,所以在CAT服务端是无法看到的对应请求。这里分享了如何在CAT服务端像同步请求一样查看到异步请求
  • SpringCloud学习 Feign远程调用 异步调用CompletableFuture 丢失请求头问题的解决 1.使用Feign进行远程调用丢失请求头问题 当使用Feign进行远程调用时,Feign会自动构造一个新的http请求再发送.因此会默认丢掉原请求...
  • 这个时候通过异步调用可以提高整体的性能。 在通过 newCall 方法创建一个新的 Call 对象之后,不是通过 execute 方法来同步执行,而是通过 enqueue 方法来添加到执行队列中。在调用 enqueue 方法时需要提供一个 ...
  • Feign异步调用丢失请求头问题

    千次阅读 2020-12-06 09:46:17
    //TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题) RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //开启第一个异步任务 CompletableFuture addressFuture...
  • 主要为大家详细介绍了SpringBoot开启异步调用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 如果请求响应比较慢,甚至请求超时,程序就必须等到请求返回以后才能继续执行,在某些场合下,我并不需要等待请求的结果,或者我不关心请求是否执行成功,需要继续执行之后的逻辑,就需要通过异步处理。 在 Spring 3...
  • 同步请求和异步请求区别

    千次阅读 2019-04-01 08:50:15
    同步请求和异步请求区别 先解释一下同步异步的概念 同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。 异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包...
  • php 异步调用方法 客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果。 有时服务器需要执行很耗时的操作,这个操作的结果并不需要返回给客户端。但因为...
  • 在实现异步调用之前我们先进行什么是同步调用和异步调用 同步:是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行 异步:是同步相对的,异步是指在...
  • C 异步调用

    千次阅读 2019-02-07 09:23:47
    C 异步调用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 410,612
精华内容 164,244
关键字:

异步调用和异步请求区别