You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot方法分块返回响应的最佳实现方式咨询

在SpringBoot中实现分块流式返回响应的最佳方式

首先先帮你理清你提到的两个概念的区别——这对理解怎么实现很重要:

  • 返回普通流对象:比如你返回一个InputStream或者InputStreamResource,Spring会把整个流的内容全部读取完毕,然后一次性打包成完整的HTTP响应发给客户端。本质还是一次性返回,根本不是分块传输,完全满足不了你“实时返回部分内容”的需求。
  • 实际流式传输(分块):这是利用HTTP的Chunked Transfer Encoding机制,把响应拆成多个独立的块,每生成一块就立刻推送给客户端,不用等所有内容都处理完。这才是你需要的“先返回part1,等10秒再返回part2”的实现方式。

接下来给你两种最实用的实现方案,都是SpringBoot里的常规操作:

方案1:使用Spring提供的StreamingResponseBody(推荐)

这是Spring专门为流式响应设计的接口,符合Spring的开发风格,还自带异步处理支持,不会阻塞Tomcat的工作线程。

修改你的方法如下:

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.StreamingResponseBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@RequestMapping(value = "/", method = RequestMethod.POST)
@SuppressWarnings("unchecked")
public ResponseEntity<StreamingResponseBody> processFile(@RequestParam("myinput") MultipartFile file) throws IOException {
    // 设置响应头,明确告诉客户端这是分块响应(Spring其实会自动处理,但手动设置更清晰)
    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.TRANSFER_ENCODING, "chunked");
    headers.add(HttpHeaders.CONTENT_TYPE, "text/plain;charset=UTF-8");
    // 禁用缓存,确保客户端能实时收到每一块内容
    headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");

    // 定义流式响应的内容逻辑
    StreamingResponseBody responseBody = outputStream -> {
        // 第一块:立即返回part1
        String part1 = "part1\n";
        outputStream.write(part1.getBytes(StandardCharsets.UTF_8));
        outputStream.flush(); // 一定要调用flush!不然内容会留在缓冲区,不会立刻发给客户端

        // 等待10秒
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("处理线程被中断", e);
        }

        // 第二块:返回part2
        String part2 = "part2\n";
        outputStream.write(part2.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();

        // 再等待15秒
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("处理线程被中断", e);
        }

        // 第三块:返回part3
        String part3 = "part3\n";
        outputStream.write(part3.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
    };

    return new ResponseEntity<>(responseBody, headers, HttpStatus.OK);
}

关键细节说明:

  • 去掉了原来的@ResponseBody和HttpServletResponse参数,因为StreamingResponseBody已经帮我们处理了响应输出。
  • 每次写完内容后必须调用flush(),否则输出流的缓冲区会攒着内容,不会实时推给客户端。
  • Spring会把StreamingResponseBody的逻辑提交到异步线程池执行,不会占用Tomcat的工作线程,适合处理大文件或耗时任务。

方案2:直接操作HttpServletResponse的输出流

如果你需要更底层的控制,也可以直接操作响应的输出流,这种方式更直观,适合新手理解分块的本质:

import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@RequestMapping(value = "/", method = RequestMethod.POST)
@SuppressWarnings("unchecked")
public void processFile(@RequestParam("myinput") MultipartFile file, HttpServletResponse response) throws IOException {
    // 设置响应头
    response.setContentType("text/plain;charset=UTF-8");
    response.setHeader(HttpHeaders.TRANSFER_ENCODING, "chunked");
    response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");

    // 获取输出流
    var outputStream = response.getOutputStream();

    try {
        // 发送第一块
        outputStream.write("part1\n".getBytes(StandardCharsets.UTF_8));
        outputStream.flush();

        // 等待10秒
        Thread.sleep(10000);

        // 发送第二块
        outputStream.write("part2\n".getBytes(StandardCharsets.UTF_8));
        outputStream.flush();

        // 等待15秒
        Thread.sleep(15000);

        // 发送第三块
        outputStream.write("part3\n".getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException("处理被中断", e);
    } finally {
        // 最后关闭流,结束响应
        outputStream.close();
    }
}

关键细节说明:

  • 这里不需要@ResponseBody,因为我们直接往响应流里写内容。
  • 一定要确保流被正确关闭(放在finally块里),避免客户端一直处于等待状态。

额外注意事项

  1. 异步处理:如果你的文件处理逻辑很耗时,建议配合Spring的@Async注解或者自定义异步线程池,避免长时间占用Tomcat的工作线程,影响服务的并发能力。
  2. 异常处理:如果中间出现异常,要及时关闭流并返回错误状态码,不然客户端会一直等待响应。
  3. 客户端测试:你可以用Postman或者curl测试分块响应——curl会实时打印每一块内容,能直观看到效果。

内容的提问来源于stack exchange,提问作者Stephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:58:48