【问题标题】:How to use servlet 3.1 in spring mvc?如何在spring mvc中使用servlet 3.1?
【发布时间】:2019-07-18 22:23:46
【问题描述】:

有 2 种不同的功能可用:

  1. servlet 3.0 允许在与容器线程不同的线程中处理请求。

  2. servlet 3.1 允许在不阻塞读/写线程的情况下读取/写入套接字

网上有很多关于 servlet 3.0 特性的例子。我们可以很容易地在 Spring 中使用它。我们只需要返回DefferedResultCompletableFuture

但我在 spring 中找不到使用 servlet 3.1 的示例。据我所知,我们必须注册WriteListenerReadListener 并在里面做圆顶脏活。但我找不到那个听众的例子。我相信这并不容易。

能否提供 Spring 中 servlet 3.1 功能的示例以及 Listener 实现的说明?

【问题讨论】:

  • 我的小费是WebFlux
  • @m4gic 正确的建议,但我想了解更多关于替代品的信息。我问这个问题是因为我想知道为什么 WebFlux 比纯 servlet 3.1 更好

标签: java spring-boot spring-mvc nio servlet-3.1


【解决方案1】:

对于 servlet 3.1,您可以使用 Reactive Streams 桥支持non-blocking I/O

Servlet 3.1+ 容器

要作为 WAR 部署到任何 Servlet 3.1+ 容器,您可以在 WAR 中扩展并包含 {api-spring-framework}/web/server/adapter/AbstractReactiveWebInitializer.html[AbstractReactiveWebInitializer]。该类使用 ServletHttpHandlerAdapter 包装一个 HttpHandler 并将其注册为 Servlet。

所以你应该扩展 AbstractReactiveWebInitializer 来增加异步支持

registration.setAsyncSupported(true);

还有ServletHttpHandlerAdapter的支持

AsyncContext asyncContext = request.startAsync();

【讨论】:

  • 异步是 servlet 3.0 的一个特性。我关于 servlet 3.1 功能的问题 - 非阻塞 IO
【解决方案2】:

如果您正在寻找 Spring/Servlet 3.1 非阻塞 HTTP API 声明的示例,请尝试以下操作:

@GetMapping(value = "/asyncNonBlockingRequestProcessing")
public CompletableFuture<String> asyncNonBlockingRequestProcessing(){
        ListenableFuture<String> listenableFuture = getRequest.execute(new AsyncCompletionHandler<String>() {
            @Override
            public String onCompleted(Response response) throws Exception {
                logger.debug("Async Non Blocking Request processing completed");
                return "Async Non blocking...";
             }
        });
        return listenableFuture.toCompletableFuture();
}

在 Servlet 容器级别需要 Spring Web 5.0+ 和 Servlet 3.1 支持(Tomcat 8.5+、Jetty 9.4+、WildFly 10+)

【讨论】:

  • 你能评论一下这段代码吗?为什么该代码非阻塞对我来说并不透明。我也不明白那段代码是做什么的
  • 此示例与 Servlet 3.1 无关,当您从 Controller 返回 CompletableFuture 时,它​​只是释放 Tomcat 线程并将此任务放入单独的线程轮询中,因此我认为此答案无关紧要
【解决方案3】:

追查一些例子应该不会太难。我在WASdev/sample.javaee7.servlet.nonblocking 找到了 IBM 的一个。在 Spring 或 Spring Boot 中使用 javax.servlet API 只需要求 Spring 注入 HttpServletRequestHttpServletResponse。所以,一个简单的例子可能是:

@SpringBootApplication
@Controller
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping(path = "")
    public void writeStream(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletOutputStream output = response.getOutputStream();
        AsyncContext context = request.startAsync();
        output.setWriteListener(new WriteListener() {
            @Override
            public void onWritePossible() throws IOException {
                if ( output.isReady() ) {
                    output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName());
                }
                context.complete();
            }
            @Override
            public void onError(Throwable t) {
                context.complete();
            }
        });
    }
}

这只是创建一个WriteListener 并将其附加到请求输出流然后返回。没什么特别的。

编辑:关键是 servlet 容器,例如 Tomcat,当数据可以被写入而不阻塞时调用onWritePossible。更多信息请访问Non-blocking I/O using Servlet 3.1: Scalable applications using Java EE 7 (TOTD #188).

侦听器(和编写器)具有回调方法,当内容可供读取或可以在不阻塞的情况下写入时调用。

因此,onWritePossible 仅在 out.println 可以在没有阻塞的情况下被调用时才被调用。

调用 setXXXListener 方法表示使用非阻塞 I/O 而不是传统的 I/O。

大概你必须做什么检查output.isReady 以了解你是否可以继续写入字节。似乎您必须与发送方/接收方就块大小达成某种隐含协议。我从未使用过它,所以我不知道,但是您要求在 Spring 框架中提供一个示例,这就是所提供的。

因此,只有在 out.println 可以不阻塞地调用时才调用 onWritePossible。 听起来是正确的,但我如何理解可以写入多少字节?我应该如何控制?

编辑 2:这是一个很好的问题,我无法给你一个确切的答案。我假设当服务器在主 servlet 的单独(异步)线程中执行代码时调用 onWritePossible。从您检查input.isReady()output.isReady() 的示例中,我假设这会阻塞您的线程,直到发送方/接收方准备好更多。由于这是异步完成的,因此服务器本身不会被阻塞并且可以处理其他请求。我从来没有用过这个,所以我不是专家。

当我说发送方/接收方会就块大小达成某种隐含协议时,这意味着如果接收方能够接受 1024 字节块,那么当output.isReady 为真时,您将写入该数量。您必须通过阅读文档来了解这一点,而 api 中没有关于它的任何内容。否则,您可以写入单个字节,但 oracle 的示例使用 1024 字节块。 1024 字节块是流式 I/O 的相当标准的块大小。上面的示例必须扩展为在 while 循环中写入字节,就像在 oracle 示例中所示。这是留给读者的练习。

Project reactor 和 Spring Webflux 有 backpressure 的概念,可以更仔细地解决这个问题。那将是一个单独的问题,我还没有仔细研究过如何将发送者和接收者结合起来(反之亦然)。

【讨论】:

  • 但你的听众只是一个存根,什么都不做
  • 它写入流并关闭它。你认为它应该做什么? output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName());
  • 来自 WriteListener 的文档-“在可以写入数据而不阻塞时调用。一旦可以写入数据,容器将在第一次请求时调用此方法。”来自 ServletOutputStream.setWriteListener -“为此 {@link ServletOutputStream} 设置 {@link WriteListener},从而切换到非阻塞 IO。”我认为涵盖了您提出的所有要点。
  • 我想海听听者的非空身体。例如写一些长的 json 字符串 (5 mb)
  • 我相信 ServletOutputStream#println 是阻塞方法
猜你喜欢
  • 1970-01-01
  • 2011-03-21
  • 2012-03-06
  • 2021-04-25
  • 2016-04-18
  • 2018-04-05
  • 2014-06-07
  • 2015-12-16
  • 1970-01-01
相关资源
最近更新 更多