如何在Spring MVC中使用Servlet 3.1?

18

有两个不同的功能可用:

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

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

互联网上有很多关于servlet 3.0功能的示例。我们可以在Spring中很容易地使用它。我们只需要返回 DefferedResultCompletableFuture

但我找不到在Spring中使用servlet 3.1的示例。据我所知,我们必须注册 WriteListenerReadListener 并在其中进行一些繁琐的操作。但我找不到这些监听器的示例。我相信这并不容易。

请提供一个使用Spring实现servlet 3.1功能的示例,并解释监听器的实现。


6
我的建议是使用WebFlux来完成此任务。 - m4gic
@m4gic 给出了正确的建议,但我想更多地了解其他选择。我问这个问题是因为我想知道为什么WebFlux比纯servlet 3.1更好。 - gstackoverflow
4个回答

1

对于servlet 3.1,您可以使用Reactive Streams bridge支持非阻塞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特性有疑问。 - gstackoverflow

0

Servlet 3.0 - 解耦容器线程和处理线程。返回DeferredResult或CompletableFuture。因此,控制器处理可以在与服务器请求处理线程不同的线程中进行。服务器线程池可以自由地处理更多的传入请求。

但是,IO仍然是阻塞的。读取和写入输入和输出流(接收和发送响应到慢客户端)。

Servlet 3.1 - 全程非阻塞,包括IO。直接在Spring上使用Servlet 3.1意味着您必须使用ReadListener和WriteListener接口,这些接口太繁琐。此外,您必须偏离使用Servlet API,如Servlet和Filter,它们是同步和阻塞的。改用支持Servlet 3.1的Spring Webflux,该框架使用Reactive Streams API。


0
如果你正在寻找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+)


3
请评论一下那段代码好吗?我不明白为什么那段代码是非阻塞的,也不理解它的作用。 - gstackoverflow
这个例子与Servlet 3.1无关,当您从控制器返回CompletableFuture时,它只会释放Tomcat线程并将此任务放入单独的线程池中,因此我认为这个答案不相关。 - Almas Abdrazak

0

追寻一些例子不应该太难。我在IBM找到了一个WASdev/sample.javaee7.servlet.nonblocking的例子。在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。更多信息请参见使用Servlet 3.1进行非阻塞I/O:使用Java EE 7构建可扩展应用程序(TOTD#188)。

侦听器(和编写器)具有回调方法,在内容可供读取或可以无阻塞地写入时调用该方法。

因此,只有在可以无阻塞地调用out.println时才会调用onWritePossible

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

大概你需要检查 output.isReady 是否就绪,才能继续写入字节。看起来你需要与发送/接收方就块大小达成某种隐式协议。我从未使用过这个,所以不清楚,但你要求在 Spring 框架中提供一个示例,这就是提供的。

因此,只有在 out.println 可以无阻塞调用时才会调用 onWritePossible。 听起来没问题,但我该如何理解可以写入多少字节?我该如何控制这个?

编辑 2:这是一个非常好的问题,我不能给你一个确切的答案。我认为当服务器在单独的(异步)线程中执行代码时,会调用 onWritePossible。从示例中,你检查 input.isReady() 或者 output.isReady(),我想这会阻塞你的线程,直到发送/接收方准备好获取更多信息。由于这是异步完成的,因此服务器本身不会被阻塞,并且可以处理其他请求。我从未使用过这个,所以我不是专家。

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

Project Reactor和Spring Webflux具有可能更加小心地处理此问题的backpressure概念。那将是一个单独的问题,我还没有深入研究如何将发送方和接收方(或反之亦然)联系起来。


它向流中写入内容并关闭它。你认为它应该做什么?output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName()); - K.Nicholas
从WriteListener的文档中 - “当可以无阻塞地写入数据时调用。容器将在请求的第一次尝试写入数据时立即调用此方法。”而从ServletOutputStream.setWriteListener - “为此{@link ServletOutputStream}设置{@link WriteListener},从而切换到非阻塞IO。” - Aditya
我想查看非空的监听器主体。例如,编写一些长的JSON字符串(5 MB)。 - gstackoverflow
我相信 ServletOutputStream#println 是一种阻塞方法。 - gstackoverflow
异步不等于非阻塞。 - gstackoverflow
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接