Spring MVC的异步模式DefferedResult

简介: 原文:http://www.importnew.com/21051.html 什么是异步模式 要知道什么是异步模式,就先要知道什么是同步模式,先看最典型的同步模式: (图1) 浏览器发起请求,Web服务器开一个线程处理,处理完把处理结果返回浏览器。

原文:http://www.importnew.com/21051.html

什么是异步模式

要知道什么是异步模式,就先要知道什么是同步模式,先看最典型的同步模式:

(图1)

浏览器发起请求,Web服务器开一个线程处理,处理完把处理结果返回浏览器。好像没什么好说的了,绝大多数Web服务器都如此般处理。现在想想如果处理的过程中需要调用后端的一个业务逻辑服务器,会是怎样呢?

(图2)

调就调吧,上图所示,请求处理线程会在Call了之后等待Return,自身处于阻塞状态。这也是绝大多数Web服务器的做法,一般来说这样做也够了,为啥?一来“长时间处理服务”调用通常不多,二来请求数其实也不多。要不是这样的话,这种模式会出现什么问题呢?——会出现的问题就是请求处理线程的短缺!因为请求处理线程的总数是有限的,如果类似的请求多了,所有的处理线程处于阻塞的状态,那新的请求也就无法处理了,也就所谓影响了服务器的吞吐能力。要更加好地发挥服务器的全部性能,就要使用异步,这也是标题上所说的“高性能的关键”。接下来我们来看看异步是怎么一回事:

(图3)

最大的不同在于请求处理线程对后台处理的调用使用了“invoke”的方式,就是说调了之后直接返回,而不等待,这样请求处理线程就“自由”了,它可以接着去处理别的请求,当后端处理完成后,会钩起一个回调处理线程来处理调用的结果,这个回调处理线程跟请求处理线程也许都是线程池中的某个线程,相互间可以完全没有关系,由这个回调处理线程向浏览器返回内容。这就是异步的过程。

带来的改进是显而易见的,请求处理线程不需要阻塞了,它的能力得到了更充分的使用,带来了服务器吞吐能力的提升。

Spring MVC的使用——DefferedResult

要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,Maven中如此配置:

<dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <version> 3.1 . 0 </version>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version> 4.2 . 3 .RELEASE</version>
     </dependency>
 

我这里使用的Servlet版本是3.1.0,Spring MVC版本是4.2.3,建议使用最新的版本。

由于Spring MVC的良好封装,异步功能使用起来出奇的简单。传统的同步模式的Controller是返回ModelAndView,而异步模式则是返回DeferredResult<ModelAndView>

看这个例子:

@RequestMapping (value= "/asynctask" , method = RequestMethod.GET)
public  DeferredResult<ModelAndView> asyncTask(){
     DeferredResult<ModelAndView> deferredResult =  new  DeferredResult<ModelAndView>();
     System.out.println( "/asynctask 调用!thread id is : "  + Thread.currentThread().getId());
     longTimeAsyncCallService.makeRemoteCallAndUnknownWhenFinish( new  LongTermTaskCallback() {
         @Override
         public  void  callback(Object result) {
             System.out.println( "异步调用执行完成, thread id is : "  + Thread.currentThread().getId());
             ModelAndView mav =  new  ModelAndView( "remotecalltask" );
             mav.addObject( "result" , result);
             deferredResult.setResult(mav);
         }
     });
}
longTimeAsyncCallService是我写的一个模拟长时间异步调用的服务类,调用之,立即返回,当它处理完成时候,就钩起一个线程调用我们提供的回调函数,这跟“图3”描述的一样,它的代码如下:
public  interface  LongTermTaskCallback {
     void  callback(Object result);
}
 
public  class  LongTimeAsyncCallService {
     private  final  int  CorePoolSize =  4 ;
     private  final  int  NeedSeconds =  3 ;
     private  Random random =  new  Random();
     private  ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(CorePoolSize);
     public  void  makeRemoteCallAndUnknownWhenFinish(LongTermTaskCallback callback){
         System.out.println( "完成此任务需要 : "  + NeedSeconds +  " 秒" );
         scheduler.schedule( new  Runnable() {
             @Override
             public  void  run() {
                 callback.callback( "长时间异步调用完成." );
             }
         },  "这是处理结果:)" , TimeUnit.SECONDS);
     }
}

输出的结果是:

/asynctask 调用!thread id is : 46
完成此任务需要 : 3 秒
异步调用执行完成, thread id is : 47

由此可见返回结果的线程和请求处理线程不是同一线程。

还有个叫WebAsyncTask

返回DefferedResult<ModelAndView>并非唯一做法,还可以返回WebAsyncTask来实现“异步”,但略有不同,不同之处在于返回WebAsyncTask的话是不需要我们主动去调用Callback的,看例子:

@RequestMapping (value= "/longtimetask" , method = RequestMethod.GET)
public  WebAsyncTask longTimeTask(){
     System.out.println( "/longtimetask被调用 thread id is : "  + Thread.currentThread().getId());
     Callable<ModelAndView> callable =  new  Callable<ModelAndView>() {
         public  ModelAndView call()  throws  Exception {
             Thread.sleep( 3000 );  //假设是一些长时间任务
             ModelAndView mav =  new  ModelAndView( "longtimetask" );
             mav.addObject( "result" "执行成功" );
             System.out.println( "执行成功 thread id is : "  + Thread.currentThread().getId());
             return  mav;
         }
     };
     return  new  WebAsyncTask(callable);
}

其核心是一个Callable<ModelAndView>,事实上,直接返回Callable<ModelAndView>都是可以的,但我们这里包装了一层,以便做后面提到的“超时处理”。和前一个方案的差别在于这个Callable的call方法并不是我们直接调用的,而是在longTimeTask返回后,由Spring MVC用一个工作线程来调用,执行,打印出来的结果:

/longtimetask被调用 thread id is : 56
执行成功 thread id is : 57

可见确实由不同线程执行的,但这个WebAsyncTask可不太符合“图3”所描述的技术规格,它仅仅是简单地把请求处理线程的任务转交给另一工作线程而已。

处理超时

如果“长时间处理任务”一直没返回,那我们也不应该让客户端无限等下去啊,总归要弄个“超时”出来。如图:

(图4)

其实“超时处理线程”和“回调处理线程”可能都是线程池中的某个线程,我为了清晰点把它们分开画而已。增加这个超时处理在Spring MVC中非常简单,先拿WebAsyncTask那段代码来改一下:

@RequestMapping (value= "/longtimetask" , method = RequestMethod.GET)
public  WebAsyncTask longTimeTask(){
     System.out.println( "/longtimetask被调用 thread id is : "  + Thread.currentThread().getId());
     Callable<ModelAndView> callable =  new  Callable<ModelAndView>() {
         public  ModelAndView call()  throws  Exception {
             Thread.sleep( 3000 );  //假设是一些长时间任务
             ModelAndView mav =  new  ModelAndView( "longtimetask" );
             mav.addObject( "result" "执行成功" );
             System.out.println( "执行成功 thread id is : "  + Thread.currentThread().getId());
             return  mav;
         }
     };
 
     WebAsyncTask asyncTask =  new  WebAsyncTask( 2000 , callable);
     asyncTask.onTimeout(
             new  Callable<ModelAndView>() {
                 public  ModelAndView call()  throws  Exception {
                     ModelAndView mav =  new  ModelAndView( "longtimetask" );
                     mav.addObject( "result" "执行超时" );
                     System.out.println( "执行超时 thread id is :"  + Thread.currentThread().getId());
                     return  mav;
                 }
             }
     );
     return  new  WebAsyncTask( 3000 , callable);
}

注意看红色字体部分代码,这就是前面提到的为什么Callable还要外包一层的缘故,给WebAsyncTask设置一个超时回调,即可实现超时处理,在这个例子中,正常处理需要3秒钟,而超时设置为2秒,所以肯定会出现超时,执行打印log如下:

/longtimetask被调用 thread id is : 59
执行超时 thread id is :61
执行成功 thread id is : 80

嗯?明明超时了,怎么还会“执行成功”呢?超时归超时,超时并不会打断正常执行流程,但注意,出现超时后我们给客户端返回了“超时”的结果,那接下来即便正常处理流程成功,客户端也收不到正常处理成功所产生的结果了,这带来的问题就是:客户端看到了“超时”,实际上操作到底有没有成功,客户端并不知道,但通常这也不是什么大问题,因为用户在浏览器上再刷新一下就好了。:D

好,再来看DefferedResult方式的超时处理:

@RequestMapping (value =  "/asynctask" , method = RequestMethod.GET)
     public  DeferredResult<ModelAndView> asyncTask() {
         DeferredResult<ModelAndView> deferredResult =  new  DeferredResult<ModelAndView>(2000L);
         System.out.println( "/asynctask 调用!thread id is : "  + Thread.currentThread().getId());
         longTimeAsyncCallService.makeRemoteCallAndUnknownWhenFinish( new  LongTermTaskCallback() {
             @Override
             public  void  callback(Object result) {
                 System.out.println( "异步调用执行完成, thread id is : "  + Thread.currentThread().getId());
                 ModelAndView mav =  new  ModelAndView( "remotecalltask" );
                 mav.addObject( "result" , result);
                 deferredResult.setResult(mav);
             }
         });
 
         deferredResult.onTimeout( new  Runnable() {
             @Override
             public  void  run() {
                 System.out.println( "异步调用执行超时!thread id is : "  + Thread.currentThread().getId());
                 ModelAndView mav =  new  ModelAndView( "remotecalltask" );
                 mav.addObject( "result" "异步调用执行超时" );
                 deferredResult.setResult(mav);
             }
         });
 
         return  deferredResult;
     }
 

非常类似,对吧,我把超时设置为2秒,而正常处理需要3秒,一定会超时,执行结果如下:

/asynctask 调用!thread id is : 48
完成此任务需要 : 3 秒
异步调用执行超时!thread id is : 51
异步调用执行完成, thread id is : 49

完全在我们预料之中。

异常处理

貌似没什么差别,在Controller中的处理和之前同步模式的处理是一样一样的:

@ExceptionHandler (Exception. class )
     public  ModelAndView handleAllException(Exception ex) {
         ModelAndView model =  new  ModelAndView( "error" );
         model.addObject( "result" , ex.getMessage());
         return  model;
     }
 
还要再弄个全局的异常处理啥的,和过去的做法都一样,在此不表了。

http://www.cnblogs.com/shihaiming/p/5856477.html

 

相关文章
|
11天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
24 4
|
29天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
110 2
|
2月前
|
存储 前端开发 测试技术
MVC、MVP、MVVM 模式
MVC、MVP 和 MVVM 是三种常见的软件架构模式,用于分离用户界面和业务逻辑。MVC(Model-View-Controller)通过模型、视图和控制器分离数据、界面和控制逻辑;MVP(Model-View-Presenter)将控制逻辑移到 Presenter 中,减少视图的负担;MVVM(Model-View-ViewModel)通过数据绑定机制进一步解耦视图和模型,提高代码的可维护性和测试性。
|
2月前
|
Java 调度 开发者
spring的@Scheduled()有几种定时模式?
【10月更文挑战第12天】spring的@Scheduled()有几种定时模式?
130 1
|
2月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
65 2
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
201 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
设计模式 Java Spring
spring源码设计模式分析(五)-策略模式
spring源码设计模式分析(五)-策略模式
|
3月前
|
设计模式 开发框架 前端开发
MVC 模式在 C# 中的应用
MVC(Model-View-Controller)模式是广泛应用于Web应用程序开发的设计模式,将应用分为模型(存储数据及逻辑)、视图(展示数据给用户)和控制器(处理用户输入并控制模型与视图交互)三部分,有助于管理复杂应用并提高代码可读性和维护性。在C#中,ASP.NET MVC框架常用于构建基于MVC模式的Web应用,通过定义模型、控制器和视图,实现结构清晰且易维护的应用程序。
65 2
|
3月前
|
消息中间件 设计模式 缓存
spring源码设计模式分析(四)-观察者模式
spring源码设计模式分析(四)-观察者模式