javascript
spring mvc 异步_DeferredResult – Spring MVC中的异步处理
spring mvc 異步
DeferredResult是一個(gè)可能尚未完成的計(jì)算的容器,它將在將來(lái)提供。 Spring MVC使用它來(lái)表示異步計(jì)算,并利用Servlet 3.0 AsyncContext異步請(qǐng)求處理。 簡(jiǎn)要介紹一下它是如何工作的:
通常,一旦離開控制器處理程序方法,請(qǐng)求處理即告完成。 但不能使用DeferredResult 。 Spring MVC(使用Servlet 3.0功能)將繼續(xù)響應(yīng),并保持空閑的HTTP連接。 HTTP工作線程不再使用,但HTTP連接仍處于打開狀態(tài)。 稍后,其他線程將通過為其分配一些值來(lái)解析DeferredResult 。 Spring MVC將立即獲取此事件并將響應(yīng)(在此示例中為“ HTTP響應(yīng):42” )發(fā)送到瀏覽器,從而完成請(qǐng)求處理。
您可能會(huì)在Future<V>和DeferredResult之間看到一些概念上的相似性–它們都代表計(jì)算,并且在將來(lái)的某個(gè)時(shí)間可用。 您可能想知道,為什么Spring MVC不允許我們簡(jiǎn)單地返回Future<V>而是引入了新的專有抽象? 原因很簡(jiǎn)單,再次顯示出Future<V>缺陷。 異步處理的全部要點(diǎn)是避免阻塞線程。 標(biāo)準(zhǔn)的java.util.concurrent.Future不允許在計(jì)算完成后注冊(cè)回調(diào)-因此,您要么需要分配一個(gè)線程來(lái)阻塞直到將來(lái)完成,要么使用一個(gè)線程來(lái)定期輪詢多個(gè)未來(lái)。 但是,后一種選擇會(huì)消耗更多的CPU并引入延遲。 但是來(lái)自番石榴的 出色ListenableFuture<V>似乎很合適? 的確如此,但是Spring沒有依賴于Guava,幸好將這兩個(gè)API橋接起來(lái)非常簡(jiǎn)單。
但是首先請(qǐng)看一下實(shí)現(xiàn)自定義java.util.concurrent.Future<V>上一部分。 誠(chéng)然,這并不像人們期望的那么簡(jiǎn)單。 清理,處理中斷,鎖定和同步,維護(hù)狀態(tài)。 當(dāng)我們需要的一切都非常簡(jiǎn)單時(shí),就像接收一條消息并從get()返回它一樣簡(jiǎn)單。 讓我們嘗試改造以前的JmsReplyFuture實(shí)現(xiàn),以實(shí)現(xiàn)更強(qiáng)大的ListenableFuture ,以便稍后在Spring MVC中使用它。
ListenableFuture只是擴(kuò)展了標(biāo)準(zhǔn) Future從而增加了注冊(cè)回調(diào)(偵聽器)的可能性。 因此,一個(gè)急切的開發(fā)人員只需坐下來(lái),然后將Runnable偵聽器列表添加到現(xiàn)有實(shí)現(xiàn)中:
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);}//...但這被大大簡(jiǎn)化了。 當(dāng)然,當(dāng)將來(lái)完成或發(fā)生異常時(shí),我們必須遍歷所有偵聽器。 如果添加偵聽器時(shí)未來(lái)已經(jīng)解決,則必須立即調(diào)用該偵聽器。 此外,我們忽略了executor -根據(jù)API,每個(gè)偵聽器都可以使用提供給addListener()的不同線程池,因此我們必須存儲(chǔ)對(duì): Runnable + Executor 。 最后但并非最不重要的一點(diǎn)addListener()不是線程安全的。 渴望的開發(fā)人員將在一兩個(gè)小時(shí)內(nèi)解決所有問題。 再花兩個(gè)小時(shí)來(lái)修復(fù)同時(shí)引入的錯(cuò)誤。 幾周后的另一個(gè)小時(shí),生產(chǎn)中又彈出了另一個(gè)“不可能的”錯(cuò)誤。 我不急。 事實(shí)上,即使是上面最簡(jiǎn)單的實(shí)現(xiàn),我也懶得寫。 但是我很拼命,要在ListenableFuture上Ctrl + H (在IntelliJ IDEA中的子類型視圖)并瀏覽可用的骨架實(shí)現(xiàn)樹。 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);}} }就這樣,一切都可以編譯并運(yùn)行。 與初始實(shí)現(xiàn)相比,代碼減少了近2 ListenableFuture并且我們獲得了更強(qiáng)大的ListenableFuture 。 大部分代碼已設(shè)置并清理。 AbstractFuture已經(jīng)為我們實(shí)現(xiàn)了addListener() ,鎖定和狀態(tài)處理。 我們要做的就是在解決未來(lái)時(shí)調(diào)用set()方法(在本例中為JMS答復(fù))。 此外,我們最終會(huì)適當(dāng)?shù)刂С之惓!?以前我們只是簡(jiǎn)單地忽略/重新拋出它們,而現(xiàn)在它們?cè)谠L問時(shí)已正確包裝并從get()拋出。 即使我們對(duì)ListenableFuture功能不感興趣, AbstractFuture仍然對(duì)我們有很大幫助。 我們免費(fèi)獲得ListenableFuture 。
好的程序員喜歡編寫代碼。 更好的人喜歡刪除它 。 更少維護(hù),更少測(cè)試,更少破壞。 有時(shí)候,我會(huì)驚訝于番石榴的功效。 上一次我使用大量的迭代器代碼。 數(shù)據(jù)是動(dòng)態(tài)生成的,迭代器可以輕松生成數(shù)百萬(wàn)個(gè)項(xiàng)目,因此我別無(wú)選擇。 有限的迭代器API和相當(dāng)復(fù)雜的業(yè)務(wù)邏輯共同構(gòu)成了無(wú)窮無(wú)盡的管道代碼。 然后我找到了Iterators實(shí)用程序類 ,它拯救了我的生命。 我建議您打開Guava的JavaDoc并逐級(jí)檢查所有軟件包。 待會(huì)兒我會(huì)謝謝你的。
一旦有了自定義的ListenableFuture (顯然您可以使用任何實(shí)現(xiàn)),我們就可以嘗試將其與Spring MVC集成。 這是我們要實(shí)現(xiàn)的目標(biāo):
使用阻止Future第一個(gè)天真的實(shí)現(xiàn):
@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();}}這種實(shí)現(xiàn)不是很幸運(yùn)。 事實(shí)上,我們根本不需要Future ,因?yàn)槲覀儙缀鯖]有阻塞get() ,而是同步等待響應(yīng)。 讓我們嘗試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; }復(fù)雜得多,但可擴(kuò)展性也更大。 此方法幾乎不需要時(shí)間來(lái)執(zhí)行,并且在準(zhǔn)備處理另一個(gè)請(qǐng)求之后不久,HTTP工作線程就已就緒。 要做的最大觀察是onSuccess()和onFailure()由另一個(gè)線程執(zhí)行,幾秒鐘甚至幾分鐘之后。 但是,HTTP工作線程池并未耗盡,并且應(yīng)用程序仍保持響應(yīng)狀態(tài)。
這是一個(gè)教科書的例子,但是我們可以做得更好嗎? 首先嘗試將通用適配器從ListenableFuture寫入DeferredResult 。 這兩個(gè)抽象代表完全相同的事物,但是具有不同的API。 這很簡(jiǎn)單:
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);}});} }我們只需擴(kuò)展DeferredResult并使用ListenableFuture回調(diào)通知它。 用法很簡(jiǎn)單:
@RequestMapping("/square/{value}") @ResponseBody public DeferredResult<String> square(@PathVariable double value) throws JMSException {final ListenableFuture<Double> responseFuture = request(value);return new ListenableFutureAdapter<>(responseFuture); }但是我們可以做得更好! 如果ListenableFuture和DeferredResult非常相似,為什么不從控制器處理程序方法中簡(jiǎn)單地返回ListenableFuture ?
@RequestMapping("/square/{value}") @ResponseBody public ListenableFuture<Double> square2(@PathVariable double value) throws JMSException {final ListenableFuture<Double> responseFuture = request(value);return responseFuture; }好吧,這是行不通的,因?yàn)镾pring無(wú)法理解ListenableFuture并會(huì)ListenableFuture 。 幸運(yùn)的是,Spring MVC非常靈活,它使我們能夠輕松注冊(cè)新的所謂的 HandlerMethodReturnValueHandler 。 有12個(gè)這樣的內(nèi)置處理程序,每當(dāng)我們從控制器返回某個(gè)對(duì)象時(shí),Spring MVC就會(huì)按預(yù)定義的順序檢查它們,然后選擇第一個(gè)可以處理給定類型的對(duì)象。 這樣的處理程序就是DeferredResultHandler (名稱已說(shuō)明一切),我們將其用作參考:
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);}}用盡業(yè)力,安裝此處理程序并不像我希望的那樣簡(jiǎn)單。 從技術(shù)上講,有WebMvcConfigurerAdapter.addReturnValueHandlers() ,如果對(duì)Spring MVC使用Java配置,我們可以輕松地覆蓋它。 但是此方法在處理程序鏈的末尾添加了自定義返回值處理程序,并且出于超出本文討論范圍的原因,我們需要在其開頭(更高優(yōu)先級(jí))添加它。 幸運(yùn)的是,通過一點(diǎn)點(diǎn)黑客攻擊,我們也可以實(shí)現(xiàn):
@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的將來(lái)/承諾抽象的另一種形式。 它用于推遲對(duì)HTTP請(qǐng)求的處理,直到完成一些異步任務(wù)。 因此, DeferredResult對(duì)于基于事件驅(qū)動(dòng)系統(tǒng),消息代理等之上的Web GUI而言非常有用。盡管它不如原始Servlet 3.0 API強(qiáng)大。 例如,我們無(wú)法在長(zhǎng)時(shí)間運(yùn)行的HTTP連接中流式傳輸多個(gè)事件(例如,新的推文)時(shí)– Spring MVC的設(shè)計(jì)更多地針對(duì)了請(qǐng)求-響應(yīng)模式。
我們還對(duì)Spring MVC進(jìn)行了調(diào)整,以允許直接從控制器方法中從Guava中返回ListenableFuture 。 它使我們的代碼更加簡(jiǎn)潔和富于表現(xiàn)力。
參考: DeferredResult –在我們的JCG合作伙伴 Tomasz Nurkiewicz的NoBlogDefFound博客中,Spring MVC中的異步處理 。
翻譯自: https://www.javacodegeeks.com/2013/03/deferredresult-asynchronous-processing-in-spring-mvc.html
spring mvc 異步
總結(jié)
以上是生活随笔為你收集整理的spring mvc 异步_DeferredResult – Spring MVC中的异步处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ddos攻击(ddos攻击查ip)
- 下一篇: ffmpeg linux编译(ffmpe