从 Spring REST 控制器返回流

2022-09-03 01:10:33

如果有可能从春天返回一个StreamRestController

@RestController
public class X {
  @RequestMapping(...)
  public Stream<?> getAll() { ... }
}

做这样的事情可以吗?我试过了,Spring返回了流值以外的其他东西。

我应该继续返回吗?List<?>


答案 1

这也可以通过Spring MVC Controller来完成,但是存在一些问题:Spring Data JPA存储库中的限制,数据库是否支持可保持游标(ResultSet Holdability)和Jackson的版本。

我很难理解的关键概念是,Java 8 Stream返回一系列在终端操作中执行的函数,因此数据库必须在执行终端操作的上下文中可访问。

春季数据 JPA 限制

我发现Spring Data JPA文档没有为Java 8 Streams提供足够的细节。看起来你可以简单地声明,但我需要注释方法才能使其工作。我也无法使用JPA标准API。因此,我不得不满足于一个硬编码的查询,例如:Stream<MyObject> readAll()@QuerySpecification

@Query("select mo from MyObject mo where mo.foo.id in :fooIds")
Stream<MyObject> readAllByFooIn(@Param("fooIds") Long[] fooIds);

可保持光标

如果具有支持可保留游标的数据库,则在提交事务后可以访问结果集。这很重要,因为我们通常使用 对类方法进行注释,因此,如果您的数据库支持可保持游标,则可以在服务方法返回后(即在方法中)访问。如果数据库不支持可保存的游标,例如MySQL,则需要将注释添加到控制器的方法中。@Service@TransactionalResultSet@Controller@Transaction@RequestMapping

所以现在 ResultSet 可以在方法之外访问,对吧?这同样取决于可持性。对于MySQL,它只能在方法中访问,因此以下内容将起作用(尽管违背了使用Java 8 Streams的整个目的):@Service@Transactional

@Transaction @RequestMapping(...)
public List<MyObject> getAll() {
   try(Stream<MyObject> stream = service.streamAll) {
        return stream.collect(Collectors.toList())
    };
}

但不是

@Transaction @RequestMapping
public Stream<MyObject> getAll() {
    return service.streamAll;
}

因为终端运营商在你的里面,所以控制器方法返回后发生在春天。@Controller

将流序列化为 JSON,而不支持“可保留游标”

若要在没有可保留游标的情况下将流序列化为 JSON,请添加到控制器方法,获取输出流并用于写入流。使用 FasterXML 3.x,您可以调用 ,但对于 2.8.x,您必须使用流的迭代器:HttpServletResponse responseObjectMapperObjectMapper().writeValue(writer, stream)

@RequestMapping(...)
@Transactional
public void getAll(HttpServletResponse response) throws IOException {
    try(final Stream<MyObject> stream = service.streamAll()) {
        final Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
        new ObjectMapper().writerFor(Iterator.class).writeValue(writer, stream.iterator());
    }
}

四. 今后的步骤

我的下一步是尝试在 中重构它,并将 JSON 序列化移动到服务中。CallableWebAsyncTask

引用


答案 2

您可以在Spring 5.0 / WebFlux中流式传输实体。

看看这个例子 反应式休息控制器 ():spring.main.web-application-type: "REACTIVE"

@RestController
public class XService {

    class XDto{
        final int x;
        public XDto(int x) {this.x = x;}
    }

    Stream<XDto> produceX(){
        return IntStream.range(1,10).mapToObj(i -> {
            System.out.println("produce "+i);
            try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
            return new XDto(i);
        });
    }

    // stream of Server-Sent Events (SSE)
    @GetMapping(value = "/api/x/sse", 
    produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<XDto> getXSse() {
        return Flux.fromStream(produceX());
    }

    // stream of JSON lines
    @GetMapping(value = "/api/x/json-stream", 
    produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
    public Flux<XDto> getAllJsonStream() {
        return Flux.fromStream(produceX());
    }

    // same as List<XDto> - blocking JSON list
    @GetMapping(value = "/api/x/json-list", 
    produces = MediaType.APPLICATION_JSON_VALUE)
    public Flux<XDto> getAll() {
        return Flux.fromStream(produceX());
    }
}

Spring Framework 5.0 - WebFlux:

Spring的响应式堆栈Web框架是5.0中新增的,它是完全响应式和非阻塞的。它适用于具有少量线程的事件循环样式处理。

服务器发送事件 (SSE):

服务器发送的事件是一种标准,用于描述一旦建立初始客户端连接,服务器如何启动向客户端的数据传输。

WebSockets vs. Server-Sent events/EventSource


推荐