原文地址:http://blog.springsource.org/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/

前面的文章中我介绍了Servlet 3、Spring MVC 3.2中支持异步的新特性,并介绍了一些实时更新的技术背景。在这篇文章中,我将展示一些Spring
MVC 3.2新特性的技术细节,以及对Spring MVC request生命周期多方面的影响。

如果需要将Controller层的方法转变为异步方法,只要将方法的返回值类型改为Callable就可以了。例如,返回视图名String类型的方法,可以改为返回Callable<String>类型;返回ResponseEntity类型的方法,可以改为返回Callable<ResponseEntity>类型;其他的返回值类型都可以以此类推。

这种处理方式中,除了Controller层方法在另外一个线程中处理完成外,其他的工作方式没有发生任何变化。当方法改变成异步处理后,保持处理方式的简单非常重要。因为你会发现,今天我仅仅讲方法改为异步方式,但还是有很多相关问题需要考虑到。

示例代码:

GitHub上spring-mvc-showcase项目中的spring-mvc-async分支里,有很多Controller层异步方法的示例。

例如下面的@ResponseBody方法,其中返回了视图名String:

  1. @RequestMapping(value="/response/annotation", method=RequestMethod.GET)
  2. public @ResponseBody Callable<String> responseBody() {
  3. return new Callable<String>() {
  4. public String call() throws Exception {
  5. // Do some work..
  6. Thread.sleep(3000L);
  7. return "The String ResponseBody";
  8. }
  9. };
  10. }

以及下面的ResponseEntity方法:

  1. @RequestMapping(value="/response/entity/headers", method=RequestMethod.GET)
  2. public Callable<ResponseEntity<String>> responseEntityCustomHeaders() {
  3. return new Callable<ResponseEntity<String>>() {
  4. public ResponseEntity<String> call() throws Exception {
  5. // Do some work..
  6. Thread.sleep(3000L);
  7. HttpHeaders headers = new HttpHeaders();
  8. headers.setContentType(MediaType.TEXT_PLAIN);
  9. return new ResponseEntity<String>(
  10. "The String ResponseBody with custom header Content-Type=text/plain", headers, HttpStatus.OK);
  11. }
  12. };
  13. }

还有redirect类型的视图名方法

  1. @RequestMapping(value="/uriTemplate", method=RequestMethod.GET)
  2. public Callable<String> uriTemplate(final RedirectAttributes redirectAttrs) {
  3. return new Callable<String>() {
  4. public String call() throws Exception {
  5. // Do some work..
  6. Thread.sleep(3000L);
  7. redirectAttrs.addAttribute("account", "a123");  // Used as URI template variable
  8. redirectAttrs.addAttribute("date", new LocalDate(2011, 12, 31));  // Appended as a query parameter
  9. return "redirect:/redirect/{account}";
  10. }
  11. };
  12. }

添加了@RequestMapping注解和@ResponseBody注解的方法中,这些注解同样会应用到返回值Callable中。添加了@ExceptionHandler注解的方法也一样,它调用了Controller层方法返回的Callable中抛出的异常。

  1. package org.springframework.samples.mvc.exceptions;
  2. import java.util.concurrent.Callable;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.ResponseBody;
  7. @Controller
  8. public class ExceptionController {
  9. @RequestMapping("/exception")
  10. public @ResponseBody Callable<String> exception() {
  11. return new Callable<String>() {
  12. public String call() throws Exception {
  13. // Do some work..
  14. Thread.sleep(2000L);
  15. throw new IllegalStateException("Sorry!");
  16. }
  17. };
  18. }
  19. @ExceptionHandler
  20. public @ResponseBody String handle(IllegalStateException e) {
  21. return "IllegalStateException handled!";
  22. }
  23. }

在GitHub中提交的这个版本,记录了其中全部更新的情况。

如果你运行了上面的任意一个方法,将会在控制台看到如下信息:

  1. 16:19:23 [http-bio-8080-exec-3] DispatcherServlet - DispatcherServlet with name 'appServlet' processing ...
  2. 16:19:23 [http-bio-8080-exec-3] RequestMappingHandlerMapping - Looking up handler method for path /views/html
  3. 16:19:23 [http-bio-8080-exec-3] RequestMappingHandlerMapping - Returning handler method ...
  4. 16:19:23 [http-bio-8080-exec-3] DispatcherServlet - Exiting request thread and leaving the response open
  5. 16:19:23 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Resuming asynchronous processing of ...
  6. 16:19:26 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Rendering view ...
  7. 16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Added model object 'fruit'
  8. 16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Added model object 'foo'
  9. 16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Forwarding to resource [/WEB-INF/views/views/html.jsp]
  10. 16:19:26 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Successfully completed request
  11. 16:19:26 [SimpleAsyncTaskExecutor-1] AsyncExecutionChainRunnable - Completing async request processing

从上面的日志信息中可以看出,Servlet容器调用的线程马上就执行完了方法,而余下的处理内容则在3秒钟后由另外一个线程完成。除了这些意外,上面的日志信息与普通的请求处理信息是一样的。

线程池配置:

正如上面的日志信息所示,返回值Callable会默认调用SimpleAsyncTaskExecutor类来处理,这个类非常简单而且没有重用线程。而在实际的产品中,你将可能会需要使用AsyncTaskExecutor类来针对你所处的环境进行适当的配置,甚至有可能你已经有了一个配置好的AsyncTaskExecutor类。可以用RequestMappingHandlerAdapter类中的asyncTaskExecutor属性来引用它。

超时设定:

超时设定是我们需要考虑的非常重要的一个方面。因为Servlet容器可能会强制将一个超时的未完成的异步请求关闭,你可以通过RequestMappingHandlerAdapter类中的asyncRequestTimeout属性指定超时时间。如果不指定超时时间,超时的时间将取决于Servlet容器所设定的时间。在Tomcat中,这个超时时间被默认设定为10秒钟(Servlet容器调用的线程执行时就开始计时)。

在超时后仍然使用一个request或response的影响是不确定的。在实际使用中,Servlet容器将尝试重用request和response对象。这样一来,避免在超时后仍然使用request和response将变得非常重要。

事实上,没有方法可以检测request是否已经超时。但是Servlet API中,当请求超时或网络出现问题时,将提供一个声明式的回调函数。Spring MVC中自动注册了这个声明,因此可以得知,一对请求响应是否不应该被使用。

下面是执行过程中的事件序列:

  1. Controller层的方法返回一个Callable值
  2. Spring MVC在另外一个线程中调用这个Callable
  3. 请求处理的主线程执行完毕退出,超时计时开始
  4. Spring MVC声明一个超时设定
  5. 将response的状态设置为503(SERVICE_UNAVAILABLE)
  6. 一段时间过后,Callable执行完毕并返回值
  7. Spring MVC抛出StaleAsyncWebRequestException异常,而并不去处理它
  8. 异常被日志记录

如果想要完全理解上面的过程,可以参考其中涉及到的三个线程——请求处理开始的线程(Servlet容器调用的线程)、执行Callable方法的线程(异步线程)和Servlet容器向Spring MVC声明超时的线程。

异常:

异步处理中HandlerExceptionResolver类和异常处理的机制没有太多不同。当返回Callable之前发生了异常,处理方式与普通异常一样;当执行Callable方法的过程中产生异常,处理方式也与普通异常一样,只不过将在当前线程(异步线程)中处理,并将仍然返回未处理的500状态的response。

ThreadLocal属性:

Spring MVC的一些部分和Spring MVC应用程序可能会以来ThreadLocal存储来获取request、locale及其他。当以异步的方式执行Callable方法时,异步线程将拥有相同的ThreadLocal属性。

OpenSessionInViewFilter和OpenSessionInViewInterceptor也被更新为以透明的方式工作。而当Controller层的方法使用了@Transactional注解时,方法返回时就将完成事务,而不会扩展到执行Callable方法的内部。如果Callable需要处理事务,则需要委托(delegate)一个事务组件。

拦截器处理:

已注册的HandlerInterceptor实例将与异步请求协作工作。主要的区别是:preHandle在Servlet容器线程开始的时候调用,而postHandle和afterCompletion方法则在异步线程中调用。在大多数情况下不会出现问题,除非HandlerInterceptor设置并清除了ThreadLocal属性。需要如此做的拦截器可能实现了AsyncHandlerInterceptor接口,这个借口为异步请求的处理添加了生命周期。

Servlet过滤器:

一些过滤器将正常工作。而其他的过滤器将尝试在Servlet容器线程退出后执行后置处理(post-processing)。这样的过滤器需要进行一定的修改用来在异步线程中完成后置处理。所有的Spring过滤器都已经按照要求(按照异步请求处理的要求)进行修改,来与异步请求协同工作。但是第三方的过滤器是否能够在Spring MVC下正常处理异步请求,取决于这些过滤器的实现细节。

总结:

在我的下一篇文章中,我将使用一个基于接收外部事件(AMQP消息)的示例,将其使用传统轮询的方式修改为使用长轮询的方式,用来在浏览器中显示实时信息。

Spring MVC 3.2 技术预览(三):动手写一个异步Controller方法的更多相关文章

  1. (转)ASP.NET MVC 第五个预览版和表单提交场景

    转自:http://ourlife.blog.51cto.com/708821/296171 上个星期四,ASP.NET MVC开发团队发布了ASP.NET MVC框架的“第五个预览版”.你可以在这里 ...

  2. Windows 10 技术预览版9926 “未知源”引起系统休眠后自启的解决办法

    问题的由来: 自从安装上了最新发布的Windows 10 ,使用起来有诸多的改进:无论是重绘的图标还是通知消息中心的整合还是更智能的OneDrive客户端都使得工作起来非常愉悦. 不过笔者这两天频繁遇 ...

  3. 新的理念、 新的解决方案、 新的Azure Stack技术预览

    Jeffrey Snover 我们很高兴地宣布︰Azure Stack Technical Preview 2(TP2)已发布!我们朝着向您的数据中心提供Azure服务能力的目标又更近一步.自发布第一 ...

  4. Windows 10 技术预览

    windows10的技术预览版已经发布了很久了,正式版大约在今年的夏天就会发布,作为微软寄予厚望的下一代全平台操作系统,相比于windows8.1,windows10做了哪些改进,又添加了哪些新功能. ...

  5. 微软推出首个Microsoft Azure Stack技术预览版

    Mike Neil,微软公司企业云副总裁 怀着对于提高业务灵活性.加速创新的期待,很多企业正在向云平台迅速迁移.伴随着这样的趋势,我们也见证了微软智能云Azure业务在全球市场的快速增长--每个月近1 ...

  6. 熊猫猪新系统测试之一:Windows 10 技术预览版

    话说本猫不用windows很多年了呀!不过看到微软最新的Windows10还是手痒了,想安装体验一把.于是第一时间下载,并做成usb引导安装镜像,在08年的老台式机上安装尝鲜鸟.下载ISO和安装方法这 ...

  7. 06、Windows 10 技术预览

    随着 Windows 10 发布的,未来 Windows 平台都是统一开发模型,可以只写一个 Appx 包,就可以同时部署到 Windows/ Windowsw Phone/ Tablet /xbox ...

  8. 微软发布Azure Stack第一个技术预览版

    为了提升商业灵敏度和加快创新步伐,各个企业都在迅速地转向云服务.在微软,我们已经见到微软智能云Azure的飞速发展和使用,每月我们都有近十万的新增订阅量.然而,我们也了解到还有很多企业在完全移到公有云 ...

  9. 第07章-Spring MVC 的高级技术

    Spring MVC 的高级技术 1. Spring MVC配置的替代方案 1.1 自定义DispatcherServlet配置 AbstractAnnotationConfigDispatcherS ...

  10. 熊猫猪新系统測试之中的一个:Windows 10 技术预览版

    话说本猫不用windows非常多年了呀! 只是看到微软最新的Windows10还是手痒了.想安装体验一把. 于是第一时间下载,并做成usb引导安装镜像,在08年的老台式机上安装尝鲜鸟.下载ISO和安装 ...

随机推荐

  1. Word在不同电脑排版异常

    Word在不同电脑排版异常 问题描述 今天又有同学向我抱怨用 word 写的论文明明在自己的电脑格式调的好好的,怎么在导师那格式又乱了,害的挨批. 笔者也遇到过该问题,正好趁这次机会简单整理一下. 注 ...

  2. Mongodb入门3

    company数据库下面heros集合里的数据: { "_id" : ObjectId("6100c897d0c9f4158c2b0c9b"), "n ...

  3. Google Analytics – GA4 & Tag Manager 概念篇

    前言 当我们设计好网站或者 App 后, 我们要怎样知道这个产品用户是否满意呢? 如果发掘潜在的提升空间呢? 等用户反馈? 投诉? 显然不是上策, 更好的方式是观察. 身为一个产品经理, 我只要看着你 ...

  4. angular cli, vs code liveserver, vs 2019 iis express 10, vs code kestrel 使用 https + ip

    更新: 2022-03-20 修订版: Vs Code, Visual Studio 2022, Angular and Live Server Running Through Https and I ...

  5. POJ-3176 Cow Bowling(基础dp)

    The cows don't use actual bowling balls when they go bowling. They each take a number (in the range ...

  6. 2019牛客暑期多校训练营(第四场)J-free(分层图最短路)

    >传送门< 题意:给你n个城市,m条道路,经过每一条要花费这条路的代价,现给你k个机会,使得最多k条路的代价为0,问从起点s到终点t花费的最少代价 思路:分层图最短路经典裸题 方法一 Co ...

  7. SSD-KD:天翼云&清华出品,最新无原始数据的蒸馏研究 | CVPR'24

    无数据知识蒸馏能够利用大型教师网络所学到的知识,来增强较小型学生网络的训练,而无需访问原始训练数据,从而避免在实际应用中的隐私.安全和专有风险.在这方面的研究中,现有的方法通常遵循一种反演蒸馏的范式, ...

  8. BOOT跳转APP,STM32F4正常,但是GD32F4起不来的问题

    问题描述:  stm32F4可以正常从BOOT跳转执行APP,到了GD32F4,卡死在APP程序的这里.  临时解决办法: APP程序内 把这两句代码都屏蔽掉就好了. 相关资料搜索: 最佳解决方案: ...

  9. Dockerfile构建镜像(八)

    一.构建镜像 现在让我们再回到之前定制的 nginx 镜像的 Dockerfile 来.现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧.在 Dockerfile 文件所在 ...

  10. 基于DPAPI+RDP技术实现本地打开远程程序,并映射到本地机器桌面上

    本教程使用工具所使用的环境说明: 启动器开发工具:VS2022 启动器所用客户端技术:.NET 8 + WPF 启动器其他技术:DPAPI 启动器发布的可执行程序,系统要求:Windows 7以及以上 ...