Spring MVC Streaming and SSE Request Processing& SSE可以实现chatgpt一次请求分批次响应的效果

1. Introduction

This simple tutorial demonstrates the use of several asynchronous and streaming objects in Spring MVC 5.x.x.

Specifically, we'll review three key classes:

  • ResponseBodyEmitter
  • SseEmitter
  • StreamingResponseBody

Also, we'll discuss how to interact with them using a JavaScript client.

2. ResponseBodyEmitter

ResponseBodyEmitter handles async responses.

Also, it represents a parent for a number of subclasses -- one of which we'll take a closer look at below.

2.1. Server Side

It's better to use a ResponseBodyEmitter along with its own dedicated asynchronous thread and wrapped with a ResponseEntity (which we can inject the emitter into directly):

复制代码
@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);
    }
}

So, in the example above, we can sidestep needing to use CompleteableFutures , more complicated asynchronous promises, or use of the @Asyncannotation.

Instead, we simply declare our asynchronous entity and wrap it in a new Thread provided by the ExecutorService.

2.2. Client Side

For client-side use, we can use a simple XHR method and call our API endpoints just like in a usual AJAX operation:

复制代码
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 is actually a subclass of ResponseBodyEmitter and provides additional Server-Sent Event (SSE) support out-of-the-box.

3.1. Server Side

So, let's take a quick look at an example controller leveraging this powerful entity:

复制代码
@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;
    }   
}

Pretty standard fare, but we'll notice a few differences between this and our usual REST controller:

  • First, we return a SseEmitter
  • Also, we wrap the core response information in its own Thread
  • Finally, we send response information usingemitter.send()

3.2. Client Side

Our client works a little bit differently this time since we can leverage the continuously connectedServer-Sent Event Library:

复制代码
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

Lastly, we can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

4.1. Server Side

复制代码
@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. Client Side

Just like before, we'll use a regular XHR method to access the controller above:

复制代码
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){ //... });
Copy

Next, let's take a look at some successful uses of these examples.

5. Bringing It All Together

After we've successfully compiled our server and run our client above (accessing the supplied index.jsp), we should see the following in our browser:

And the following in our terminal:

We can also call the endpoints directly and see them streaming responses appear in our browser.

本文通过三类对象(ResponseBodyEmitterSseEmitterStreamingResponseBody)展示了 Spring MVC 处理异步与流式响应的不同方案,涵盖服务端实现、客户端交互及效果验证,适用于实时数据推送、大文件传输等场景。

类名 特点
ResponseBodyEmitter 基础异步响应,需手动管理线程与数据发送。
SseEmitter 支持 SSE 协议,适合服务器主动推送多次事件(如实时通知)。
StreamingResponseBody 直接操作输出流,适合大文件下载或低层级流式数据处理。
相关推荐
Sylvia-girl3 小时前
Java——抽象类
java·开发语言
Touper.6 小时前
Redis 基础详细介绍(Redis简单介绍,命令行客户端,Redis 命令,Java客户端)
java·数据库·redis
m0_535064606 小时前
C++模版编程:类模版与继承
java·jvm·c++
虾条_花吹雪7 小时前
Using Spring for Apache Pulsar:Message Production
java·ai·中间件
tomorrow.hello7 小时前
Java并发测试工具
java·开发语言·测试工具
Moso_Rx7 小时前
javaEE——synchronized关键字
java·java-ee
张小洛7 小时前
Spring AOP 是如何生效的(入口源码级解析)?
java·后端·spring
DKPT8 小时前
Java设计模式之行为型模式(观察者模式)介绍与说明
java·笔记·学习·观察者模式·设计模式
追风少年浪子彦8 小时前
mapstruct与lombok冲突原因及解决方案
java·spring boot·spring·spring cloud
军军君018 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具四:后端项目基础框架搭建下
spring boot·spring·面试·elementui·typescript·uni-app·mybatis