现在的位置: 首页 > 综合 > 正文

什么是SpringMvc异步处理

2020年02月21日 综合 ⁄ 共 3437字 ⁄ 字号 评论关闭

  Web容器对异步的支持

  SpringMvc的实质就是对Java Web的封装,因此为了更容易的理解SpringMvc的异步,必须先了解Java Web的异步。

  Java Web的实质就是一套标准(接口),Web容器实现了这套标准,所以我们站在Web容器的立场来说Java Web的异步,非常好理解。

  无论采用什么框架开发的Java Web应用,最终都是在Web容器中运行的,如tomcat就是最常用的Web容器。

  启动SpringBoot时,可以看看日志中打印出的线程名称,其实就是tomcat线程池里的线程。可以多请求几次,发现每次处理请求的线程Id都不一样。

  我们知道,其实每一个请求过来后,Web容器都会从自己的线程池中拿出一个线程来运行Java Web应用以处理请求。当请求处理完后,这个线程会被还回到线程池中以便处理下一个请求。

  如果请求能在非常短的时间内处理完成,是没有问题的。我们设想一下,如果请求执行的非常慢,那么这个线程将无法还回去,于是线程池中可用的线程数目将少一个。

  由于线程池的容量是有限的,如果很多执行的很慢的请求同时到来,那么线程池中的线程将会用光,而且短时间内都还不回来。此时的其它请求都将无法被处理。

  我们发现执行很快的请求和非常耗时的请求是属于两种不同性质的事物,现在它们都由Web容器的线程池的线程来从头处理到尾,就像当初的小王。

  既要接待客户做咨询工作,又要等客户走了自己干活。这显然是低效的,唯一的方法就是将这两种工作分开,分别交由两个岗位去做。

  于是我们就看到,小王招了很多工人,自己专业做咨询接活儿,转手把活儿交给工人去做,工人做好后把活儿再交回给小王,小王再向业主交付。

  我们可以把这个思路套到Web容器上,就是Web容器线程池的线程只用来处理执行速度很快的请求,这对应于小王只负责咨询接活儿。

  对于非常耗时的请求,Web容器线程池的线程将把这个请求交给其它专门的线程池的线程去处理,这对应于小王把接到的活儿交给他的工人去做。

  Web容器线程池的线程将再去接受其它请求,因为耗时的请求已经被交出去了。这对应于小王再去接别的活儿,因为上一个活儿已经交给工人去干了。

  专门的线程池的线程处理完这个请求后会把它还回给Web容器线程池的线程,这对应于工人干完活儿后会把活儿先交给小王去验收。

  Web容器线程池的线程拿到处理结果,把结果写入响应,这个请求就被处理完毕了。这对应于小王把工人干好的活儿交付给业主,施工就算完毕了。

  我们来分析一下,小王由于角色转变而带来的工作转变都有哪些。一开始小王单打独斗,小王工作有三种,小王从业主接活儿,小王自己干活,小王向业主交付。

  到最后小王成了工长,此时的工作是四种,小王从业主接活儿,小王把活儿交给工人去做,工人把做好的活儿交回给小王,小王向业主交付。

  对比后发现,小王为了使自己不干活,所以引入了工人,以及由此产生的他与工人之间的一去一来的协调交互。

  同样来分析一下,Web容器线程池的线程前前后后有哪些变化。一开始线程处理所有的请求,可以分为三个阶段,线程接受请求,线程处理请求,线程写回响应。

  到最后线程的工作分为四个阶段,线程接受请求,线程把待处理的请求交给专门的线程池,专门的线程池把处理好的请求交回给线程,线程写回响应。

  对比后发现,Web容器线程池的线程为了使自己不处理耗时请求,所以引入了专门的线程池,以及由此产生的它与专门的线程池的一去一来的协调交互。

  是不是发现线程池和小王的行为是完全对应的。而且他们面临的问题都一样,就是把需要处理的任务交出去给别人,别人把处理完毕的任务再还回给他。

  好了,现在我来告诉你,这就是Java Web异步处理的整体逻辑思想。化繁为简后,其实就是一去一来这两个动作罢了。

  自己把执行流程交出去:

  1AsyncContext startAsync()

  别人把执行流程还回来:

  1void dispatch()

  上面这两个方法就完成了一去一来这两个动作,仅此而已。

  看到这些,可能有些人大失所望,传说中“牛X和高大上”的异步处理,到最后也不过区区十四个字,“执行流程交出去,执行流程还回来”。

  可能还会有人觉得,异步处理不应该和客户端也有关系吗?其实并没有,纯粹是服务器端的把戏。而且对于单个请求的处理时间也不会减少。

  就像你去饭店吃饭,你点的这份饭在后厨是一个厨师完成的还是多个厨师协作完成的,其实你并不知道,一般情况下你也并不关心。

  SpringMvc对异步的支持

  上面描述的异步处理逻辑只是一个规范(接口),是不带实现的。任何想要支持Java Web异步处理的,需要在遵守这个规范的前提下,自己提供一套实现。

  说白了,就是也要采用交出去还回来的模式,但是如何交出去,怎样还回来,要自己去实现,还有这个专门的线程池也要自己来提供。

  SpringMvc提供了对异步的支持,所以它遵守了这个规范,而且它也定义了相似的接口来与Java Web规范交互。

  这个接口就是AsyncWebRequest,它的实现类是StandardServletAsyncWebRequest。同样的两个方法。

  交出去:

  1void startAsync()

  还回来:

  1void dispatch()

  在这两个方法的实现中,分别去调用了Java Web规范中的对应方法,这就是与Java Web规范的交互。

  同时SpringMvc自己是有线程池的,所以在第一个方法里实现了把执行流程交出去的操作,在第二个方法里实现了把执行流程还回来的操作。

  还有一个问题就是,并不是所有的方法都需要异步处理,所以就需要有一种方式来告诉SpringMvc,哪个方法需要异步处理。

  SpringMvc选择了采用方法返回值的类型来进行区分,凡是@RequestMapping方法的返回值类型是以下这些的,就表明开发人员想进行异步处理,于是SpringMvc就进行异步处理。

  1Callable< V> 2 3DeferredResult< T> 4 5WebAsyncTask< V> 6 7StreamingResponseBody 8 9ResponseEntity< StreamingResponseBody>1011ResponseBodyEmitter1213ResponseEntity< ResponseBodyEmitter>

  对于异步处理的方式,其实也包括两大类,一类是开发人员不管,完全交给SpringMvc去处理,一类是开发人员自己把控,不用SpringMvc参与。

  对于第一类,我们返回Callable< V>类型就可以了,这样SpringMvc会把它提交到线程池里去执行。

  对于第二类,我们返回DeferredResult< T>类型即可,它的意思是延迟结果,就是需要延迟一会才会有结果,但是如何延迟呢,SpringMvc并不会去管。

  因此是由开发人员来决定的,开发人员需要自己弄个线程去执行,并且一定要记住最后要设置一下结果才行。

  我们看到耗时的代码都跑到别的线程里去执行了,那么SpringMvc的处理主流程自然就结束了,这就是执行流程交出去的过程。

  只不过有一点需要注意,这个请求的处理并没有完成,只是暂时离开了SpringMvc的主流程,在别的线程池里运行着呢。

  那么一段时间后,别的线程池运行结束,已经取得了结果,那这个结果和执行流程又该如何还回来呢?

  直接还回来吗?显然是不可能的,因为SpringMvc的处理主流程在把任务交出去的那一刻就已经结束了、不存在了。

  其实是别的线程池在处理结束并得到结果后,处理流程连同结果会返回到Web容器中,由Web容器再次分派这个请求到SpringMvc中来。

  这就是为什么上面第二个方法的名字叫做dispatch的原因,就是再次分派这个请求到Web应用中来嘛,因此会再次进入到SpringMvc中,这不就把执行流程还回来了嘛。

  可能会有人产生疑惑,如果SpringMvc再次处理这个请求那不就乱套了吗?答案是显然不会的,因为这次异步的结果已经存在了,自然不会再异步处理了,而是把它跳过去直接进入后续处理。

  也就是对方法返回值(ReturnValue)的处理,比如序列化成JSON写入响应,或进行视图渲染,把渲染后的视图写入响应,这样这个请求就算处理完毕了。

抱歉!评论已关闭.