`
twincle
  • 浏览: 41523 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

Spring MVC 3.2 技术预览(三):动手写一个异步Controller方法

阅读更多

原文地址: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:

 

@RequestMapping(value="/response/annotation", method=RequestMethod.GET)
	public @ResponseBody Callable<String> responseBody() {

		return new Callable<String>() {
			public String call() throws Exception {

				// Do some work..
				Thread.sleep(3000L);

				return "The String ResponseBody";
			}
		};
	}

 

        以及下面的ResponseEntity方法:

 

@RequestMapping(value="/response/entity/headers", method=RequestMethod.GET)
	public Callable<ResponseEntity<String>> responseEntityCustomHeaders() {

		return new Callable<ResponseEntity<String>>() {
			public ResponseEntity<String> call() throws Exception {

				// Do some work..
				Thread.sleep(3000L);

				HttpHeaders headers = new HttpHeaders();
				headers.setContentType(MediaType.TEXT_PLAIN);
				return new ResponseEntity<String>(
						"The String ResponseBody with custom header Content-Type=text/plain", headers, HttpStatus.OK);
			}
		};
	}

 

        还有redirect类型的视图名方法

 

@RequestMapping(value="/uriTemplate", method=RequestMethod.GET)
	public Callable<String> uriTemplate(final RedirectAttributes redirectAttrs) {

		return new Callable<String>() {
			public String call() throws Exception {

				// Do some work..
				Thread.sleep(3000L);

				redirectAttrs.addAttribute("account", "a123");  // Used as URI template variable
				redirectAttrs.addAttribute("date", new LocalDate(2011, 12, 31));  // Appended as a query parameter
				return "redirect:/redirect/{account}";
			}
		};
	}

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

 

package org.springframework.samples.mvc.exceptions;

import java.util.concurrent.Callable;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ExceptionController {

	@RequestMapping("/exception")
	public @ResponseBody Callable<String> exception() {

		return new Callable<String>() {
			public String call() throws Exception {

				// Do some work..
				Thread.sleep(2000L);

				throw new IllegalStateException("Sorry!");
			}
		};
	}

	@ExceptionHandler
	public @ResponseBody String handle(IllegalStateException e) {
		return "IllegalStateException handled!";
	}

}

 

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

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

 

16:19:23 [http-bio-8080-exec-3] DispatcherServlet - DispatcherServlet with name 'appServlet' processing ...
16:19:23 [http-bio-8080-exec-3] RequestMappingHandlerMapping - Looking up handler method for path /views/html
16:19:23 [http-bio-8080-exec-3] RequestMappingHandlerMapping - Returning handler method ...
16:19:23 [http-bio-8080-exec-3] DispatcherServlet - Exiting request thread and leaving the response open
16:19:23 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Resuming asynchronous processing of ...
16:19:26 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Rendering view ...
16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Added model object 'fruit'
16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Added model object 'foo'
16:19:26 [SimpleAsyncTaskExecutor-1] JstlView - Forwarding to resource [/WEB-INF/views/views/html.jsp]
16:19:26 [SimpleAsyncTaskExecutor-1] DispatcherServlet - Successfully completed request
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消息)的示例,将其使用传统轮询的方式修改为使用长轮询的方式,用来在浏览器中显示实时信息。

分享到:
评论
1 楼 nanyangwenfeng 2012-07-12  
期待更新,写的很好!!!

相关推荐

    达内云笔记项目

    Java+MyBatis3.2+SpringMVC+Spring3.2+JQuery2.1 Java:基础的核心技术 MyBatis:访问数据库 SpringMVC:对代码分层,实现MVC,重点在于处理请求 Spring:管理组件、整合MyBatis、处理一些通用的业务...

    达内 云笔记 很实用!

    Java+MyBatis3.2+SpringMVC+Spring3.2+JQuery2.1 Java:基础的核心技术 MyBatis:访问数据库 SpringMVC:对代码分层,实现MVC,重点在于处理请求 Spring:管理组件、整合MyBatis、处理一些通用的业务...

    看透springMvc源代码分析与实践

    22.2.1 Spring MVC中异步请求相关组件286 22.2.2 Spring MVC对异步请求的支持297 22.2.3 WebAsyncTask和Callable类型异步请求的处理过程及用法301 22.2.4 DeferredResult类型异步请求的处理过程及用法303 22.2.5...

    Spring-Reference_zh_CN(Spring中文参考手册)

    2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6 .3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 ...

    Spring中文帮助文档

    使用@Controller定义一个控制器 13.12.3. 使用@RequestMapping映射请求 13.12.4. 使用@RequestParam绑定请求参数到方法参数 13.12.5. 使用@ModelAttribute提供一个从模型到数据的链接 13.12.6. 使用@...

    Spring.3.x企业应用开发实战(完整版).part2

    Spring3.0是Spring在积蓄了3年之久后,隆重推出的一个重大升级版本,进一步加强了Spring作为Java领域第一开源平台的翘楚地位。  Spring3.0引入了众多Java开发者翘首以盼的新功能和新特性,如OXM、校验及格式化框架...

    springboot学习思维笔记.xmind

    @Bean注解在方法上,声明当前方法的返回值为一个Bean AOP @Aspect 声明是一个切面 拦截规则@After @Before @Around PointCut JoinPoint Spring常用配置 Bean的Scope Singleton ...

    Spring API

    使用@Controller定义一个控制器 13.12.3. 使用@RequestMapping映射请求 13.12.4. 使用@RequestParam绑定请求参数到方法参数 13.12.5. 使用@ModelAttribute提供一个从模型到数据的链接 13.12.6. 使用@...

    java面试题

    答:多形:一个类中多个同名方法。继承:子类继承父类。 jsp内置对象? 答:request 用户端请求 response 回应 pageContext 网页属性 session 会话 out 输出 page 当前网页 exception 错误网页 application ...

    Spring3.x企业应用开发实战(完整版) part1

    Spring3.0是Spring在积蓄了3年之久后,隆重推出的一个重大升级版本,进一步加强了Spring作为Java领域第一开源平台的翘楚地位。  Spring3.0引入了众多Java开发者翘首以盼的新功能和新特性,如OXM、校验及格式化框架...

    spring-webflux-research:Springwebflux研究

    spring-webflux依然沿用了与spring-webmvc相同的Controller注解和路由方式,对于旧项目迁移至新项目中带来了便利。中间层的业务代码由Reactive Stream方式管理,Reactive Streams默认采用Reactor框架,同时还支持另...

    毕业设计基于ssm框架的购物管理系统源码+数据库

    项目结构描述: 该项目中有五个子模块。 shop-manager模块负责导入该项目所需要的所有依赖包。 shop-web模块负责存放前端代码以及项目的配置文件。 shop-controller模块负责...后台使用spring+spring mvc+mybatis框架。

    JAVA毕业设计之springboot063知识管理系统(springboot+mysql)完整源码.zip

    以下是关于该项目的500字资源介绍:项目结构:本项目采用经典的MVC(Model-View-Controller)架构,分为前端、后端和数据库三个部分。前端使用HTML、CSS和JavaScript进行页面设计和交互实现;后端使用Spring Boot...

    java网上购物系统(毕业设计专用!!)帮导入,帮运行!

    这是一个基于ssm框架的购物系统 适用于即将毕业的计算机专业大学生的毕业项目 本项目用 IDEA 2019 Navicat15 javaJDK1.8 tomcat8.5.76 Maven3.6.1 ### 项目结构描述: 1. 该项目中有五个子模块。 2. shop-manager...

    SSM-ShopProject:这是一个基于ssm框架的购物系统

    这是一个基于ssm框架的购物系统 项目结构描述: 该项目中有五个子模块。 shop-manager模块负责导入该项目所需要的所有依赖包。 shop-web模块负责存放前端代码以及项目的配置文件。 shop-controller模块负责存放项目...

    smart-doc是一款同时支持JAVA REST API和Apache Dubbo RPC接口文档生成的工具,.rar

    支持Spring MVC、Spring Boot、Spring Boot Web Flux(controller书写方式)、Feign。 支持Callable、Future、CompletableFuture等异步接口返回的推导。 支持JavaBean上的JSR303参数校验规范,包括分组验证。 对JSON...

    springmvc注解

    spring mvc基于注解的简单小例子 里面有拦截器, 国际化 ajax异步请求 二维码生成 解析 多文件上传功能 但都是写在一个controller 里面 自己在jsp页面改一下请求方法名就可以 有所需要的所有jar包 下载可直接使用

    115-springboot-demo-smart-doc.rar

    支持Spring MVC、Spring Boot、Spring Boot Web Flux(controller书写方式)、Feign。支持Callable、Future、CompletableFuture等异步接口返回的推导。支持JavaBean上的JSR303参数校验规范,包括分组验证。对JSON请求...

    java面试宝典

    (如:一对多、多对多的关系) 22 99、说下Hibernate的缓存机制 22 100、Hibernate的查询方式 23 101、如何优化Hibernate? 23 102、Struts工作机制?为什么要使用Struts? 23 103、Struts的validate框架是如何验证的...

    SpringMVC+Jquery实现Ajax功能

    SpringMVC:是基于Spring的一个子框架(MVC框架),功能强于Spring,这个框架主要是解决咱们Controller这一层的问题。 M:model-模型User V:view-视图jsp C:Controller-控制器servlet jQuery框架:是一个程序员使用...

Global site tag (gtag.js) - Google Analytics