使用Spring SseEmitter时如何处理异常并向SSE客户端发送错误信息
问题1:是否可以通过
@ExceptionHandler处理SSE场景的异常? 常规的@ControllerAdvice+@ExceptionHandler方案只能捕获控制器方法执行过程中同步抛出的异常,如果你的控制器已经返回了SseEmitter对象,后续的业务逻辑是在独立的异步线程中执行的,已经脱离了原请求的处理上下文,@ControllerAdvice无法感知到这个线程抛出的异常,因此不能直接用原生机制处理该场景的异步异常。
如果要复用全局异常处理的逻辑,可以做如下适配:
- 将
@ExceptionHandler中的异常转换逻辑封装为公共的工具方法 - 在SSE异步业务逻辑的最外层加统一try-catch,捕获到异常后调用上述公共方法得到标准化的错误对象,再通过SSE通道发送给客户端
如果异常是在控制器返回SseEmitter之前抛出的(比如参数校验失败、项目不存在的判断在同步逻辑中),此时SSE连接还未建立,@ExceptionHandler可以正常捕获异常,直接返回带错误信息的HTTP响应即可。
问题2:Spring SSE的使用误区说明
你现在调用completeWithError()方法返回空错误信息是符合Spring的设计逻辑的,该方法的作用是直接中断SSE连接、返回对应HTTP错误状态码,不会将你传入的异常对象序列化后发送给客户端,浏览器的EventSource的onerror回调只能拿到状态码,无法获取自定义错误内容。
正确的自定义错误发送逻辑是:将错误信息作为普通SSE事件发送,发送完成后再正常结束连接,代码示例如下:
服务端修改
private void sendError(String message, int status) { log.error("Processing report {} stopped with error '{}'", getContextLogMessage(), message); try { // 构造自定义错误事件发送给客户端 sseEmitter.send(SseEmitter.event() // 指定事件名,方便前端针对性监听 .name("custom_error") .data(new ApiError(message, HttpStatus.resolve(status))) .build()); } catch (IOException e) { // 处理发送失败场景,一般为客户端提前断开连接 log.error("Failed to send error event to client", e); } finally { // 正常结束连接,不要用completeWithError sseEmitter.complete(); sseEmitter.onCompletion(this::stopSelf); } }
客户端修改
const EventSource = require('eventsource') const eventSource = new EventSource('http://localhost:8080/testing') eventSource.onmessage = (e) => { const data = JSON.parse(e.data) if (data.status == null) { console.log(data) } else if (data.status === 'DONE') { console.log(data.status); eventSource.close() } else { console.log('status = ' + data.status) } } // 监听自定义的错误事件 eventSource.addEventListener('custom_error', (e) => { const error = JSON.parse(e.data) console.log('收到服务端错误', error) eventSource.close() }) // 原onerror保留,用于处理底层连接错误、网络异常等场景 eventSource.onerror = (e) => { console.log('连接异常', e) eventSource.close() }
内容的提问来源于stack exchange,提问作者NikMashei
相关产品推荐
相关产品推荐

