Spring Boot 默认使用 Tomcat 作为嵌入式 Servlet 服务器,它是由 Apache 软件基金会下 Jakarta 项目开发的 Servlet 容器,被广泛用于部署和运行 Java Web 应用程序。特点是轻量级、易于安装和配置,并且具有良好的可扩展性和性能
本文将会介绍另一款优秀的 Servlet 容器,Undertow。以及如何在 Spring Boot 中用它来替换 Tomcat 作为嵌入式服务器。
Undertow {#undertow}
Undertow 是一个轻量级的、高性能的 Java Web 服务器,由 JBoss 开发并开源。它是基于非阻塞(non-blocking)的I/O模型,具有低资源消耗和高并发处理能力。
Undertown 的优势如下:
- 支持 HTTP/2:Undertow 开箱即支持 HTTP/2,无需重写启动类路径。
- 支持 HTTP Upgrade:允许通过 HTTP 端口复用多种协议。
- 支持 Web Socket:Undertow 提供对 Web Sockets 的全面支持,包括 JSR-356 支持。
- Servlet 4.0:Undertow 支持 Servlet 4.0,包括对嵌入式 Servlet 的支持。还可以在同一部署中混合使用 Servlet 和原生 undertow 非阻塞 handler。
- 可嵌入式:只需几行代码,即可将 Undertow 嵌入应用程序或独立运行。
- 灵活性:Undertow 通过链式 handler 进行配置,可以根据需求灵活地添加功能。
在很多场景的测试下, Undertow 的性能都高于 Tomcat。天生适合作为 Spring Boot 应用的嵌入式服务器!
在 Spring Boot 中使用 {#在-spring-boot-中使用}
如上所述,Spring Boot 默认使用 Tomcat 作为嵌入式服务。所以 spring-boot-starter-web 默认依赖了 spring-boot-starter-tomcat。
要使用 Undertow 首先要从 spring-boot-starter-web 排除 spring-boot-starter-tomcat,再添加 spring-boot-starter-undertow 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
仅此即可。启动应用,查看日志:
INFO 11908 --- [           main] io.undertow                              : starting server: Undertow - 2.3.8.Final
INFO 11908 --- [           main] org.xnio                                 : XNIO version 3.8.8.Final
INFO 11908 --- [           main] org.xnio.nio                             : XNIO NIO Implementation Version 3.8.8.Final
INFO 11908 --- [           main] org.jboss.threads                        : JBoss Threads version 3.5.0.Final
INFO 11908 --- [           main] o.s.b.w.e.undertow.UndertowWebServer     : Undertow started on port(s) 8080 (http)
如你所见,Spring Boot 已经使用 Undertow 作为嵌入式服务器。
配置属性 {#配置属性}
Spring Boot 预置了很多属性,可用于在 applicaton.properties | yaml 中对 Undertow 服务器进行个性化配置。
它们都以 server.undertow.* 开头,总结如下:
|                    配置项                     |                                                      说明                                                       |      示例       |
|--------------------------------------------|---------------------------------------------------------------------------------------------------------------|---------------|
| server.undertow.accesslog.dir            | Undertow 访问日志目录。                                                                                              |               |
| server.undertow.accesslog.enabled        | 是否启用访问日志。                                                                                                     | false       |
| server.undertow.accesslog.pattern        | 访问日志的格式。                                                                                                      | common      |
| server.undertow.accesslog.prefix         | 日志文件前缀。                                                                                                       | access_log. |
| server.undertow.accesslog.rotate         | 是否开启日志滚动。                                                                                                     | true        |
| server.undertow.accesslog.suffix         | 日志文件后缀。                                                                                                       | log         |
| server.undertow.always-set-keep-alive    | 是否应在所有响应中添加 Connection: keep-alive Header,即使 HTTP 规范没有要求。                                                   | true        |
| server.undertow.buffer-size              | 每个 buffer 的大小。默认大小是根据 JVM 可用的最大内存确定的。                                                                         |               |
| server.undertow.decode-slash             | 是否应解码已编码的斜线字符(%2F)。如果前端代理不执行相同的解码,解码可能会导致安全问题。只有在传统应用程序需要时才启用。设置后,server.undertow.allow-encoded-slash 无效。 |               |
| server.undertow.decode-url               | 是否对 URL 进行解码。禁用时,URL 中的百分比编码字符将保持原样。                                                                          | true        |
| server.undertow.direct-buffers           | 是否在 Java 堆外分配 buffer。默认大小是根据 JVM 可用的最大内存确定的。                                                                  |               |
| server.undertow.eager-filter-init        | 是否应在启动时初始化 servlet Filter                                                                                     | true        |
| server.undertow.max-cookies              | 允许的最大 cookie 数量。这一限制是为了防止基于哈希碰撞的 DOS 攻击。                                                                      | 200         |
| server.undertow.max-headers              | 允许的最大 header 数量。这一限制是为了防止基于哈希碰撞的 DOS 攻击。                                                                      |               |
| server.undertow.max-http-post-size       | HTTP post content 的最大大小。当值为-1(默认值)时,大小为无限。                                                                    | -1B         |
| server.undertow.max-parameters           | 允许查询或路径参数的最大数量。这一限制是为了防止基于哈希碰撞的 DOS 攻击。                                                                       |               |
| server.undertow.no-request-timeout       | 在服务器关闭连接之前,连接在不处理请求的情况下闲置的时间。                                                                                 |               |
| server.undertow.options.server.*         | 在 io.undertow.UndertowOptions 中定义的服务器选项。                                                                    |               |
| server.undertow.options.socket.*         | 在 org.xnio.Options 中定义的 socket 选项。                                                                          |               |
| server.undertow.preserve-path-on-forward | 转发请求时是否保留请求路径。                                                                                                | false       |
| server.undertow.threads.io               | I/O 线程数。默认值为可用的处理器数量。                                                                                         |               |
| server.undertow.threads.worker           | Worker 线程数。默认为 I/O 线程数的 8 倍。                                                                                  |               |
| server.undertow.url-charset              | 用于解码 URL 的字符集。                                                                                                | UTF-8       |
编程式配置 {#编程式配置}
如果配置属性无法满足你的需求,你可以通过配置类,以编程式的方式进行定制。
实现 WebServerFactoryCustomizer<UndertowServletWebServerFactory> 接口,覆写 customize 方法:
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory>{
@Override
public void customize(UndertowServletWebServerFactory factory) {
    factory.setBufferSize(8192);
    // 其他自定义配置 ...
}
}
对于
UndertowServletWebServerFactory配置类的细节,请参阅 官方文档。
启动时的警告日志 {#启动时的警告日志}
当你使用 Undertow 作为 Spring Boot 嵌入式服务器时,启动应用。你会看到有一条 WARN 日志,如下:
WARN 9608 --- [           main] io.undertow.websockets.jsr               : UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
大致意思是"没有给 WebSocketDeploymentInfo 设置 Buffer pool,将会使用默认值"。有 2 种方式可以解决这个问题。
排除 undertow-websockets-jsr 依赖 {#排除-undertow-websockets-jsr-依赖}
如果未使用到 WebSocket 技术,那么可以直接从 spring-boot-starter-undertow 中排除 undertow-websockets-jsr 依赖即可。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
    <exclusions>
        <!-- 排除 undertow-websockets-jsr 依赖 -->
        <exclusion>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-websockets-jsr</artifactId>
        </exclusion>
    </exclusions>
</dependency>
为 WebSocketDeploymentInfo 设置合理的参数 {#为-websocketdeploymentinfo-设置合理的参数}
也可以通过上述的 "编程式" 配置方式,为 WebSocketDeploymentInfo 设置一个合理的参数。如下:
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
@Configuration
public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory>{
@Override
public void customize(UndertowServletWebServerFactory factory) {
    factory.addDeploymentInfoCustomizers(deploymentInfo -> {
        
        WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
        
        // 设置合理的参数
        webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
        
        deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
    });
}
}
经过测试,上述 2 种方式都可以解决 Undertow 启动时有警告日志的问题。
总结 {#总结}
总之,使用 Undertow 作为 Spring Boot 的嵌入式服务器可以提供更好的性能、更小的内存占用以及更高的灵活性和嵌入式支持。
 51工具盒子
51工具盒子 
                 
                             
                         
                         
                        