1. 簡介
本教程演示了在 Spring MVC 5.x.x 中使用多個異步和流式對象的方法。
具體來説,我們將回顧三個關鍵類:
- ResponseBodyEmitter
- SseEmitter
- StreamingResponseBody
此外,我們還將討論如何使用 JavaScript 客户端與之交互。
2. ResponseBodyEmitter
ResponseBodyEmitter 用於處理異步響應。
它還代表了多個子類的父類之一,其中我們將更詳細地進行分析。
2.1. 服務器端
最好使用 ResponseBodyEmitter 及其專用的異步線程,並將其包裹在 ResponseEntity 中(我們可以直接注入 emitter )。
@Controller
public class ResponseBodyEmitterController {
private ExecutorService executor
= Executors.newCachedThreadPool();
@GetMapping("/rbe")
public ResponseEntity<ResponseBodyEmitter> handleRbe() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
executor.execute(() -> {
try {
emitter.send(
"/rbe" + " @ " + new Date(), MediaType.TEXT_PLAIN);
emitter.complete();
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return new ResponseEntity(emitter, HttpStatus.OK);
}
}因此,在上面的示例中,我們可以避免使用 CompleteableFutures,更復雜的異步承諾,或者使用 @Async註解。
相反,我們只需聲明異步實體並將其包裝在 ExecutorService提供的新的 Thread中。
2.2. 客户端
為了客户端使用,我們可以使用簡單的 XHR 方法並像普通 AJAX 操作一樣調用我們的 API 端點:
var xhr = function(url) {
return new Promise(function(resolve, reject) {
var xmhr = new XMLHttpRequest();
//...
xmhr.open("GET", url, true);
xmhr.send();
//...
});
};
xhr('http://localhost:8080/javamvcasync/rbe')
.then(function(success){ //... });
3. SseEmitter
SseEmitter 實際上是 ResponseBodyEmitter 的子類,並提供內置的 服務器發送事件 (SSE) 支持。
3.1. 服務器端
下面我們來看一個利用該強大實體的示例控制器:
@Controller
public class SseEmitterController {
private ExecutorService nonBlockingService = Executors
.newCachedThreadPool();
@GetMapping("/sse")
public SseEmitter handleSse() {
SseEmitter emitter = new SseEmitter();
nonBlockingService.execute(() -> {
try {
emitter.send("/sse" + " @ " + new Date());
// we could send more events
emitter.complete();
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return emitter;
}
}
情況相當典型,但與我們常規的 REST 控制器相比,這裏有一些差異:
- 首先,我們返回一個
- 此外,我們將核心響應信息包裝在一個
- 最後,我們使用
- 此外,我們將核心響應信息包裝在一個
3.2. 客户端
我們的客户端這次採用了稍微不同的方式,因為我們可以利用持續連接的Server-Sent Event庫:
var sse = new EventSource('http://localhost:8080/javamvcasync/sse');
sse.onmessage = function (evt) {
var el = document.getElementById('sse');
el.appendChild(document.createTextNode(evt.data));
el.appendChild(document.createElement('br'));
};4. StreamingResponseBody
最後,我們可以使用StreamingResponseBody直接將數據寫入OutputStream,然後再使用ResponseEntity將寫入的信息返回給客户端。
4.1. 服務器端
@Controller
public class StreamingResponseBodyController {
@GetMapping("/srb")
public ResponseEntity<StreamingResponseBody> handleRbe() {
StreamingResponseBody stream = out -> {
String msg = "/srb" + " @ " + new Date();
out.write(msg.getBytes());
};
return new ResponseEntity(stream, HttpStatus.OK);
}
}
4.2. 客户端
正如之前一樣,我們將使用常規的 XHR 方法來訪問上述控制器:
var xhr = function(url) {
return new Promise(function(resolve, reject) {
var xmhr = new XMLHttpRequest();
//...
xmhr.open("GET", url, true);
xmhr.send();
//...
});
};
xhr('http://localhost:8080/javamvcasync/srb')
.then(function(success){ //... });
接下來,讓我們來查看一下這些示例的成功應用。
5. 整合所有內容
在成功編譯我們的服務器並運行上述客户端(訪問提供的 index.jsp)後,我們應該在瀏覽器中看到以下內容:
並且在終端中看到以下內容:
我們還可以直接調用端點並觀察響應在瀏覽器中流式傳輸。
6. 結論
儘管 Future 和 CompleteableFuture 在 Java 和 Spring 中已經證明了其強大的功能,但現在我們擁有了更多資源,可以更有效地處理高度併發的 Web 應用程序中的異步和流式數據。