DeferredResult – Spring MVC中的异步处理

DeferredResult是一个可能尚未完成的计算的容器,它将在将来提供。 Spring MVC使用它来表示异步计算,并利用Servlet 3.0 AsyncContext异步请求处理。 简要介绍一下它是如何工作的:

@RequestMapping("/")
@ResponseBody
public DeferredResult<String> square() throws JMSException {final DeferredResult<String> deferredResult = new DeferredResult<>();runInOtherThread(deferredResult);return deferredResult;
}private void runInOtherThread(DeferredResult<String> deferredResult) {//seconds later in other thread...deferredResult.setResult("HTTP response is: 42");
}

通常,一旦离开控制器处理程序方法,请求处理即告完成。 但不能使用DeferredResult 。 Spring MVC(使用Servlet 3.0功能)将继续响应,并保持空闲HTTP连接。 HTTP工作线程不再使用,但HTTP连接仍处于打开状态。 稍后,其他线程将通过为其分配一些值来解析DeferredResult 。 Spring MVC将立即拾取此事件并将响应(在此示例中为“ HTTP响应:42” )发送到浏览器,从而完成请求处理。

您可能会在Future<V>DeferredResult之间看到一些概念上的相似性–它们都代表计算,并且在将来的某个时间可用。 您可能想知道,为什么Spring MVC不允许我们简单地返回Future<V>而是引入了新的专有抽象? 原因很简单,再次显示出Future<V>缺陷。 异步处理的全部要点是避免阻塞线程。 标准的java.util.concurrent.Future不允许在计算完成后注册回调-因此,您要么需要分配一个线程来阻塞直到将来完成,要么使用一个线程来定期轮询多个未来。 但是,后一种选择会消耗更多的CPU并引入延迟。 但是来自番石榴的 出色ListenableFuture<V>似乎很合适? 的确如此,但是Spring没有依赖于Guava,幸好将这两个API桥接起来非常简单。

但是首先请看一下实现自定义java.util.concurrent.Future<V>上一部分。 诚然,这并不像人们期望的那么简单。 清理,处理中断,锁定和同步,维护状态。 当我们需要的一切都像接收一条消息并从get()返回它一样简单时,就会有很多样板。 让我们尝试改造以前的JmsReplyFuture实现,以实现更强大的ListenableFuture ,以便稍后在Spring MVC中使用它。

ListenableFuture只是扩展了标准 Future从而增加了注册回调(侦听器)的可能性。 因此,一个急切的开发人员只需坐下来,然后将Runnable侦听器列表添加到现有实现中:

public class JmsReplyFuture<T extends Serializable> implements ListenableFuture<T>, MessageListener {private final List<Runnable> listeners = new ArrayList<Runnable>();@Overridepublic void addListener(Runnable listener, Executor executor) {listeners.add(listener);}//...

但这被大大简化了。 当然,当将来完成或发生异常时,我们必须遍历所有侦听器。 如果添加侦听器时未来已经解决,则必须立即调用该侦听器。 此外,我们忽略了executor -根据API,每个侦听器都可以使用提供给addListener()的不同线程池,因此我们必须存储对: Runnable + Executor 。 最后但并非最不重要的一点addListener()不是线程安全的。 渴望的开发人员将在一两个小时内解决所有问题。 再花两个小时来修复同时引入的错误。 几小时后的另一个小时,生产中又弹出了另一个“不可能的”错误。 我不急。 事实上,即使是上面最简单的实现,我也懒得写。 但是我很拼命,要在ListenableFutureCtrl + H (在IntelliJ IDEA中的子类型视图)并浏览可用的骨骼实现树。 AbstractFuture<V> –宾果游戏!

public class JmsReplyListenableFuture<T extends Serializable> extends AbstractFuture<T> implements MessageListener {private final Connection connection;private final Session session;private final MessageConsumer replyConsumer;public JmsReplyListenableFuture(Connection connection, Session session, Queue replyQueue) throws JMSException {this.connection = connection;this.session = session;this.replyConsumer = session.createConsumer(replyQueue);this.replyConsumer.setMessageListener(this);}@Overridepublic void onMessage(Message message) {try {final ObjectMessage objectMessage = (ObjectMessage) message;final Serializable object = objectMessage.getObject();set((T) object);cleanUp();} catch (Exception e) {setException(e);}}@Overrideprotected void interruptTask() {cleanUp();}private void cleanUp() {try {replyConsumer.close();session.close();connection.close();} catch (Exception e) {Throwables.propagate(e);}}
}

就这样,一切都可以编译并运行。 与初始实现相比,代码减少了近2 ListenableFuture并且我们获得了更强大的ListenableFuture 。 大部分代码已设置并清理。 AbstractFuture已经为我们实现了addListener() ,锁定和状态处理。 我们要做的就是在解决未来时调用set()方法(在我们的情况下,JMS答复到达)。 此外,我们最终会适当地支持异常。 以前我们只是简单地忽略/重新抛出它们,而现在它们在访问时已正确包装并从get()抛出。 即使我们对ListenableFuture功能不感兴趣, AbstractFuture仍然对我们有很大帮助。 我们免费获得ListenableFuture

好的程序员喜欢编写代码。 更好的人喜欢删除它 。 更少维护,更少测试,更少破坏。 有时我会惊讶于番石榴能提供多大的帮助。 上一次我使用大量的迭代器代码。 数据是动态生成的,迭代器可以轻松生成数百万个项目,因此我别无选择。 有限的迭代器API和相当复杂的业务逻辑共同构成了无数管道代码。 然后我找到了Iterators实用程序类 ,它拯救了我的生命。 我建议您打开Guava的JavaDoc并逐一检查所有软件包。 待会儿我会谢谢你的。

一旦有了自定义的ListenableFuture (显然您可以使用任何实现),我们就可以尝试将其与Spring MVC集成。 这是我们要实现的目标:

  1. HTTP请求进来
  2. 我们向JMS队列发送请求
  3. HTTP工作线程不再使用,它​​可以处理其他请求
  4. JMS侦听器异步等待临时队列中的答复
  5. 回复到达后,我们立即将其作为HTTP响应推送并完成连接。

使用阻止Future第一个天真的实现:

@Controller
public class JmsController {private final ConnectionFactory connectionFactory;public JmsController(ConnectionFactory connectionFactory) {this.connectionFactory = connectionFactory;}@RequestMapping("/square/{value}")@ResponseBodypublic String square(@PathVariable double value) throws JMSException, ExecutionException, InterruptedException {final ListenableFuture<Double> responseFuture = request(value);return responseFuture.get().toString();}//JMS API boilerplateprivate <T extends Serializable> ListenableFuture<T> request(Serializable request) throws JMSException {Connection connection = this.connectionFactory.createConnection();connection.start();final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);final Queue tempReplyQueue = session.createTemporaryQueue();final ObjectMessage requestMsg = session.createObjectMessage(request);requestMsg.setJMSReplyTo(tempReplyQueue);sendRequest(session.createQueue("square"), session, requestMsg);return new JmsReplyListenableFuture<T>(connection, session, tempReplyQueue);}private void sendRequest(Queue queue, Session session, ObjectMessage requestMsg) throws JMSException {final MessageProducer producer = session.createProducer(queue);producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);producer.send(requestMsg);producer.close();}}

这种实现不是很幸运。 事实上,我们根本不需要Future ,因为我们几乎没有阻塞get() ,而是同步等待响应。 让我们尝试DeferredResult

@RequestMapping("/square/{value}")
@ResponseBody
public DeferredResult<String> square(@PathVariable double value) throws JMSException {final DeferredResult<String> deferredResult = new DeferredResult<>();final ListenableFuture<Double> responseFuture = request(value);Futures.addCallback(responseFuture, new FutureCallback<Double>() {@Overridepublic void onSuccess(Double result) {deferredResult.setResult(result.toString());}@Overridepublic void onFailure(Throwable t) {deferredResult.setErrorResult(t);}});return deferredResult;
}

复杂得多,但可扩展性也更大。 该方法几乎不需要时间来执行,并且HTTP工作线程在准备处理另一个请求之后不久。 要做的最大观察是onSuccess()onFailure()由另一个线程执行,几秒钟甚至几分钟之后。 但是,HTTP工作线程池并未耗尽,并且应用程序仍保持响应状态。

这是一个教科书的例子,但是我们可以做得更好吗? 首先尝试将通用适配器从ListenableFuture写入DeferredResult 。 这两个抽象代表完全相同的事物,但是具有不同的API。 这很简单:

public class ListenableFutureAdapter<T> extends DeferredResult<String> {public ListenableFutureAdapter(final ListenableFuture<T> target) {Futures.addCallback(target, new FutureCallback<T>() {@Overridepublic void onSuccess(T result) {setResult(result.toString());}@Overridepublic void onFailure(Throwable t) {setErrorResult(t);}});}
}

我们只需扩展DeferredResult并使用ListenableFuture回调通知它。 用法很简单:

@RequestMapping("/square/{value}")
@ResponseBody
public DeferredResult<String> square(@PathVariable double value) throws JMSException {final ListenableFuture<Double> responseFuture = request(value);return new ListenableFutureAdapter<>(responseFuture);
}

但是我们可以做得更好! 如果ListenableFutureDeferredResult非常相似,为什么ListenableFuture从控制器处理程序方法中返回ListenableFuture

@RequestMapping("/square/{value}")
@ResponseBody
public ListenableFuture<Double> square2(@PathVariable double value) throws JMSException {final ListenableFuture<Double> responseFuture = request(value);return responseFuture;
}

好吧,这是行不通的,因为Spring无法理解ListenableFuture并且只会ListenableFuture 。 幸运的是,Spring MVC非常灵活,它使我们能够轻松注册新的所谓的 HandlerMethodReturnValueHandler 。 有12个这样的内置处理程序,每当我们从控制器返回某个对象时,Spring MVC就会按预定义的顺序检查它们,然后选择第一个可以处理给定类型的对象。 这样的处理程序之一就是DeferredResultHandler (名称说明了一切),我们将其用作参考:

public class ListenableFutureReturnValueHandler implements HandlerMethodReturnValueHandler {public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();return ListenableFuture.class.isAssignableFrom(paramType);}public void handleReturnValue(Object returnValue,MethodParameter returnType, ModelAndViewContainer mavContainer,NativeWebRequest webRequest) throws Exception {if (returnValue == null) {mavContainer.setRequestHandled(true);return;}final DeferredResult<Object> deferredResult = new DeferredResult<>();Futures.addCallback((ListenableFuture<?>) returnValue, new FutureCallback<Object>() {@Overridepublic void onSuccess(Object result) {deferredResult.setResult(result.toString());}@Overridepublic void onFailure(Throwable t) {deferredResult.setErrorResult(t);}});WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);}}

用尽业力,安装此处理程序并不像我希望的那样简单。 从技术上讲,有WebMvcConfigurerAdapter.addReturnValueHandlers() ,如果对Spring MVC使用Java配置,我们可以轻松地覆盖它。 但是此方法在处理程序链的末尾添加了自定义返回值处理程序,并且出于超出本文讨论范围的原因,我们需要在其开头添加它(优先级更高)。 幸运的是,通过一点点黑客攻击,我们也可以实现:

@Configuration
@EnableWebMvc
public class SpringConfig extends WebMvcConfigurerAdapter {@Resourceprivate RequestMappingHandlerAdapter requestMappingHandlerAdapter;@PostConstructpublic void init() {final List<HandlerMethodReturnValueHandler> originalHandlers = new ArrayList<>(requestMappingHandlerAdapter.getReturnValueHandlers().getHandlers());originalHandlers.add(0, listenableFutureReturnValueHandler());requestMappingHandlerAdapter.setReturnValueHandlers(originalHandlers);}@Beanpublic HandlerMethodReturnValueHandler listenableFutureReturnValueHandler() {return new ListenableFutureReturnValueHandler();}}

摘要

在本文中,我们熟悉了称为DeferredResult的将来/承诺抽象的另一种形式。 它用于推迟对HTTP请求的处理,直到完成一些异步任务。 因此, DeferredResult对于基于事件驱动系统,消息代理等之上的Web GUI而言非常有用。尽管它不如原始Servlet 3.0 API强大。 例如,我们无法在长时间运行的HTTP连接中流式传输多个事件(例如,新的推文)时– Spring MVC的设计更多地是针对请求-响应模式。

我们还对Spring MVC进行了调整,以允许直接从控制器方法中从Guava中返回ListenableFuture 。 它使我们的代码更加简洁和富于表现力。

参考: DeferredResult –在我们的JCG合作伙伴 Tomasz Nurkiewicz的NoBlogDefFound博客中,Spring MVC中的异步处理 。

翻译自: https://www.javacodegeeks.com/2013/03/deferredresult-asynchronous-processing-in-spring-mvc.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单的GTK窗体搭建

#include<gtk/gtk.h> //必须引用gtk/gtk.h这个头文件2 int main(int argc,char *argv[]) //标准c语言主函数的声明3 {4 GtkWidget *window; //声明一个窗口控件的指针&#xff0c;其中GtkWidget是…

有关输出图形的代码,我觉得好难啊,好蒙啊。

这里的代码其实没看懂过&#xff0c;自己看到书上这一题的时候也是挺蒙的&#xff0c;压根不知道要怎么下手&#xff0c;照着书上把代码打进去之后也不清楚原理&#xff0c;可怕的是&#xff0c;反反复复对着答案敲了几遍代码&#xff0c;执行结果还是这样的&#xff0c;和课本…

《Web前端开发修炼之道》-读书笔记CSS部分

如何组织CSS-分层 应用 css 的能力分两部分&#xff1a;一部分是css的API&#xff0c;重点是如何用css控制页面内元素的样式&#xff1b;另一部分是css框架&#xff0c;重点是如何对 css 进行组织。如何组织 css 可以有多种角度&#xff0c;例如按功能划分&#xff0c;或者按区…

Windows环境变量的应用

设置环境变量快速打开程序 如果你对桌面上密密麻麻的文件感到烦恼&#xff0c;那么下面的方法可以帮到你。 Step 1 在硬盘的某个位置添加一个文件夹&#xff0c;把你经常用的程序的快捷方式放进这个文件夹&#xff0c;快捷方式重命名最好简单易记&#xff0c;如图 注意 快捷方式…

mysql 备份数据库_mysql数据库备份

前一段时间因为误操作删除了一张表的几条数据&#xff0c;弄得很尴尬&#xff0c;正好这周有空就折腾了下数据备份的知识&#xff0c;现把mysql的数据备份相关实践和心得总结如下&#xff1a;一.使用mysqldump命令备份数据库&#xff1a;备份整个数据库(包括表结构和数据),用法…

JavaScript:Browser 对象

ylbtech-JavaScript&#xff1a;Browser 对象1. Window 对象返回顶部 1、Window 对象 Window 对象 Window 对象表示浏览器中打开的窗口。 如果文档包含框架&#xff08;<frame> 或 <iframe> 标签&#xff09;&#xff0c;浏览器会为 HTML 文档创建一个 window 对象…

使用Apache KeyedObjectPool的ssh连接池

我发现org.apache.commons.pool非常有用且健壮&#xff0c;但没有充分记录。 因此&#xff0c;我将在这里帮助您解释如何使用Apache KeyedObjectPool 。 什么是KeyedObjectPool &#xff1f; 它是一个映射&#xff0c;其中包含多种类型的实例池。 可以使用任意键访问每种类型。…

c语言怎么输入有空格的字符串

c语言怎么输入有空格的字符串 https://blog.csdn.net/qq_44752641/article/details/106118698 采用fgets输入可以识别空格,如输入hello world&#xff0c;输出hello world char str[50]; printf("输入一段字符串&#xff1a;"); fgets(str,50,stdin);若采用scanf&…

深入理解line-height与vertical-align——前端布局常用属性

line-height、font-size、vertical-align是设置行内元素布局的关键属性。这三个属性是相互依赖的关系&#xff0c;改变行间距离、设置垂直对齐等都需要它们的通力合作。下面将主要介绍line-height与vertical-align&#xff1a; 行高 【定义】 line-height行高是指文本行基线之…

UWP开发入门(四)——自定义CommandBar

UWP开发入门&#xff08;四&#xff09;——自定义CommandBar 原文:UWP开发入门&#xff08;四&#xff09;——自定义CommandBar各位好&#xff0c;再次回到UWP开发入门系列&#xff0c;刚回归可能有些不适应&#xff0c;所以今天我们讲个简单的&#xff0c;自定义CommandBar&…

(转)Cobbler无人值守批量安装Linux系统

本文目录&#xff1a; 1.1 pxe安装系统 1.2 cobbler基本介绍 1.3 安装和配置cobbler 1.3.1 安装cobbler 1.3.2 配置dhcp和tftp 1.4 cobbler从本地光盘安装系统 1.4.1 生成distro 1.4.2 提供kickstart文件 1.4.3 提供profile 1.4.4 开始安装 1.5 比pxekickstart好的地方 1.6 让新…

mysql 数据库 应用_MySQL数据库的应用

1、创建一个以你名字为名的数据库&#xff0c;并创建一张表student&#xff0c;该表包含三个字段(id&#xff0c;name&#xff0c;age)&#xff0c;表结构如下&#xff1a;mysql>desc student;---------------------------------------------------------| Field | Type | N…

Spring集成–从头开始应用程序,第1部分

开始之前 在本教程中&#xff0c;您将学习什么是Spring Integration &#xff0c;如何使用它以及有助于解决哪些问题。 我们将从头开始构建一个示例应用程序&#xff0c;并演示Spring Integration的一些核心组件。 如果您不熟悉Spring&#xff0c;请查看我编写的另一本有关Spri…

初学者Web介绍一些前端开发中的基本概念用到的技术

Web开发是比较费神的&#xff0c;需要掌握很多很多的东西&#xff0c;特别是从事前端开发的朋友&#xff0c;需要通十行才行。今天&#xff0c;本文向初学者介绍一些Web开发中的基本概念和用到的技术&#xff0c;从A到Z总共26项&#xff0c;每项对应一个概念或者技术。 初学者W…

幻方问题

把1~16的数字填入4x4的方格中&#xff0c;使得行、列以及两个对角线的和都相等&#xff0c;满足这样的特征时称为&#xff1a;四阶幻方。 四阶幻方可能有很多方案。如果固定左上角为1&#xff0c;请计算一共有多少种方案。 比如&#xff1a; 1 2 15 16 12 14 3 5 13 7 10 4 8 …

Xcode 快捷键及代码格式化

按住apple键点击类名就可以定位到这个类中查看相关定义&#xff08;在日后的开发中我们会经常这么来做&#xff0c;毕竟要记住iOS开发中所有的API是不现实的&#xff0c;有些API我们可以通过这种方法来查找&#xff09; PS&#xff1a;下面都是网上百度后经过我自己整理&#x…

mysql 4字节utf8_MySQL 4字节utf8字符更新失败一例

MySQL 4字节utf8字符更新失败一例业务的小伙伴反映了下面的问题问题有一个4字节的utf8字符????插入到MySQL数据库中时报错java.sql.SQLException: Incorrect string value: \xF0\xA0\x99\xB6 for column c_utf8mb4 at row 1数据库中存放该字符的列已经定义为utf8mb4编码了&…

主席树学习小结(POJ 2104)

在高中的时候就听到过主席树了&#xff0c;感觉非常高端&#xff0c;在寒假的时候 winter homework中有一题是查找区间第K大的树&#xff0c;当时就开始百度这种网上的博客&#xff0c;发现主席树看不懂&#xff0c;因为那个root[i]&#xff0c;还有tx[x].l与tx[x].r是什么意思…

通过Spring Social推特StackExchange –第1部分

本文将介绍一个快速的附带项目-一个自动从各种Q&#xff06;A StackExchange网站上发布热门问题的机器人&#xff0c;例如StackOverflow &#xff0c; ServerFault &#xff0c; SuperUser等。我们将为StackExchange API构建一个简单的客户端&#xff0c;然后进行设置使用Sprin…

下拉菜单

<!Doctype html> <html> <head> <meta charset"utf-8"> <title>下拉菜单</title> <style> *{ margin:0; padding:0; } ul{ list-style:none; overflow:hidden; background-color:#333; } li{ float:left; } li a,.drop…