跳到主要内容

嵌入式 Web 服务器

DeepSeek V3 中英对照 Embedded Web Servers

每个 Spring Boot Web 应用程序都包含一个嵌入式 Web 服务器。这一特性引发了许多关于“如何实现”的问题,包括如何更改嵌入式服务器以及如何配置嵌入式服务器。本节将回答这些问题。

使用另一个 Web 服务器

许多 Spring Boot 启动器都包含了默认的嵌入式容器。

  • 对于 servlet 栈应用程序,spring-boot-starter-web 通过包含 spring-boot-starter-tomcat 来引入 Tomcat,但你也可以使用 spring-boot-starter-jettyspring-boot-starter-undertow 替代。

  • 对于响应式栈应用程序,spring-boot-starter-webflux 通过包含 spring-boot-starter-reactor-netty 来引入 Reactor Netty,但你也可以使用 spring-boot-starter-tomcatspring-boot-starter-jettyspring-boot-starter-undertow 替代。

在切换到不同的 HTTP 服务器时,您需要将默认依赖项替换为所需的依赖项。为了简化此过程,Spring Boot 为每个受支持的 HTTP 服务器提供了一个单独的 starter。

以下 Maven 示例展示了如何在 Spring MVC 中排除 Tomcat 并包含 Jetty:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
xml

以下 Gradle 示例配置了必要的依赖项和一个模块替换,以便在 Spring WebFlux 中使用 Undertow 替代 Reactor Netty:

dependencies {
implementation "org.springframework.boot:spring-boot-starter-undertow"
implementation "org.springframework.boot:spring-boot-starter-webflux"
modules {
module("org.springframework.boot:spring-boot-starter-reactor-netty") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty")
}
}
}
gradle
备注

使用 WebClient 类需要 spring-boot-starter-reactor-netty,因此即使你需要包含不同的 HTTP 服务器,也可能需要保留对 Netty 的依赖。

禁用 Web 服务器

如果你的类路径中包含了启动 Web 服务器所需的依赖,Spring Boot 将会自动启动它。要禁用此行为,可以在 application.properties 中配置 WebApplicationType,如下例所示:

spring.main.web-application-type=none
properties

更改 HTTP 端口

在独立应用程序中,HTTP 主端口默认设置为 8080,但可以通过 server.port 进行配置(例如在 application.properties 中或作为系统属性)。得益于 Environment 值的宽松绑定,你也可以使用 SERVER_PORT(例如作为操作系统环境变量)。

要完全关闭 HTTP 端点,但仍创建 WebApplicationContext,可以使用 server.port=-1(这样做有时对测试很有用)。

更多详细信息,请参阅“Spring Boot 特性”部分中的自定义嵌入式 Servlet 容器,或查看 ServerProperties 类。

使用一个随机未分配的 HTTP 端口

要扫描一个空闲端口(使用操作系统原生方法以防止冲突),请使用 server.port=0

运行时发现 HTTP 端口

你可以通过日志输出或从 WebServerApplicationContext 通过其 WebServer 访问服务器运行的端口。获取该端口并确保它已初始化的最佳方法是添加一个类型为 ApplicationListener<WebServerInitializedEvent>@Bean,并在事件发布时从事件中提取容器。

使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 的测试类也可以通过 @LocalServerPort 注解将实际的端口号注入到字段中,如下例所示:

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {

@LocalServerPort
int port;

// ...

}
java
备注

@LocalServerPort@Value("${local.server.port}") 的一个元注解。不要在常规应用程序中尝试注入端口。正如我们刚刚看到的,该值只有在容器初始化之后才会被设置。与测试不同,应用程序代码的回调会提前处理(在实际值可用之前)。

启用 HTTP 响应压缩

HTTP 响应压缩在 Jetty、Tomcat、Reactor Netty 和 Undertow 中均受支持。可以在 application.properties 中启用,如下所示:

server.compression.enabled=true
properties

默认情况下,响应长度必须至少为 2048 字节才会进行压缩。你可以通过设置 server.compression.min-response-size 属性来配置此行为。

默认情况下,仅当响应的内容类型为以下之一时,才会对其进行压缩:

  • text/html

  • text/xml

  • text/plain

  • text/css

  • text/javascript

  • application/javascript

  • application/json

  • application/xml

你可以通过设置 server.compression.mime-types 属性来配置此行为。

配置 SSL

可以通过设置各种 server.ssl.* 属性来以声明式的方式配置 SSL,通常是在 application.propertiesapplication.yaml 文件中进行配置。有关所有支持的属性的详细信息,请参阅 Ssl

以下示例展示了使用 Java KeyStore 文件设置 SSL 属性的方法:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret
properties

使用上述示例中的配置意味着应用程序不再支持在端口 8080 上的普通 HTTP 连接器。Spring Boot 不支持通过 application.properties 同时配置 HTTP 连接器和 HTTPS 连接器。如果你想要同时拥有两者,你需要以编程方式配置其中一个。我们建议使用 application.properties 来配置 HTTPS,因为 HTTP 连接器在这两者中更容易以编程方式配置。

使用 PEM 编码的文件

你可以使用 PEM 编码的文件来代替 Java KeyStore 文件。应尽可能使用 PKCS#8 密钥文件。PEM 编码的 PKCS#8 密钥文件以 -----BEGIN PRIVATE KEY----------BEGIN ENCRYPTED PRIVATE KEY----- 开头。

如果你有其他格式的文件,例如 PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) 或 SEC 1 (-----BEGIN EC PRIVATE KEY-----),你可以使用 OpenSSL 将它们转换为 PKCS#8:

openssl pkcs8 -topk8 -nocrypt -in <input file> -out <output file>
shell

以下示例展示了如何使用 PEM 编码的证书和私钥文件设置 SSL 属性:

server.port=8443
server.ssl.certificate=classpath:my-cert.crt
server.ssl.certificate-private-key=classpath:my-cert.key
server.ssl.trust-certificate=classpath:ca-cert.crt
properties

使用 SSL 捆绑包

或者,SSL 信任材料可以在 SSL 包 中进行配置,并应用于 Web 服务器,如下例所示:

server.port=8443
server.ssl.bundle=example
properties
备注

server.ssl.bundle 属性不能与 server.ssl 下的离散 Java KeyStore 或 PEM 属性选项结合使用。

使用 bundle 时,server.ssl.ciphersserver.ssl.enabled-protocolsserver.ssl.protocol 属性也会被忽略。这些属性应通过 spring.ssl.bundle.<type>.<name>.options 属性来定义。

配置服务器名称指示

Tomcat、Netty 和 Undertow 可以配置为为各个主机名使用唯一的 SSL 信任材料,以支持服务器名称指示(Server Name Indication,SNI)。Jetty 不支持 SNI 配置,但如果向 Jetty 提供多个证书,它可以自动设置 SNI

假设已经配置了名为 webweb-alt1web-alt2SSL 捆绑包,以下配置可用于将每个捆绑包分配给嵌入式 Web 服务器所服务的主机名:

server.port=8443
server.ssl.bundle=web
server.ssl.server-name-bundles[0].server-name=alt1.example.com
server.ssl.server-name-bundles[0].bundle=web-alt1
server.ssl.server-name-bundles[1].server-name=alt2.example.com
server.ssl.server-name-bundles[1].bundle=web-alt2
properties

在指定了 server.ssl.bundle 的情况下,该证书包将用于默认主机以及任何不支持 SNI(服务器名称指示)的客户端。如果配置了任何 server.ssl.server-name-bundles,则必须配置此默认证书包。

配置 HTTP/2

你可以通过在 Spring Boot 应用程序中配置 server.http2.enabled 属性来启用 HTTP/2 支持。h2(基于 TLS 的 HTTP/2)和 h2c(基于 TCP 的 HTTP/2)都受支持。要使用 h2,必须同时启用 SSL。如果未启用 SSL,则会使用 h2c。例如,当你的应用程序在运行在代理服务器后面且该代理服务器执行 TLS 终止时,你可能希望使用 h2c

在 Tomcat 中配置 HTTP/2

Spring Boot 默认搭载了 Tomcat 10.1.x,它原生支持 h2ch2。另外,如果主机操作系统上安装了 libtcnative 库及其依赖项,你也可以使用它来支持 h2

如果尚未将库目录提供给 JVM 库路径,则必须将其提供。你可以通过 JVM 参数来实现,例如 -Djava.library.path=/usr/local/opt/tomcat-native/lib。更多信息请参阅 官方 Tomcat 文档

使用 Jetty 配置 HTTP/2

为了支持 HTTP/2,Jetty 需要额外的 org.eclipse.jetty.http2:jetty-http2-server 依赖。要使用 h2c,不需要其他依赖。要使用 h2,你还需要根据部署环境选择以下依赖之一:

  • org.eclipse.jetty:jetty-alpn-java-server 用于使用 JDK 内置支持

  • org.eclipse.jetty:jetty-alpn-conscrypt-serverConscrypt 库

使用 Reactor Netty 配置 HTTP/2

默认情况下,spring-boot-webflux-starter 使用 Reactor Netty 作为服务器。Reactor Netty 原生支持 h2ch2。为了获得最佳的运行时性能,该服务器还支持使用原生库的 h2。要启用此功能,您的应用程序需要添加一个额外的依赖项。

Spring Boot 管理了 io.netty:netty-tcnative-boringssl-static "uber jar" 的版本,该 jar 包含了所有平台的本地库。开发者可以选择使用分类器仅导入所需的依赖项(请参阅 Netty 官方文档)。

使用 Undertow 配置 HTTP/2

Undertow 开箱即支持 h2ch2

配置 Web 服务器

通常,你应该首先考虑使用众多可用的配置键之一,并通过在 application.propertiesapplication.yaml 文件中添加新条目来自定义你的 Web 服务器。请参阅 发现外部属性的内置选项server.* 命名空间在这里非常有用,它包括 server.tomcat.*server.jetty.* 等命名空间,用于特定服务器的功能。请参阅 常见应用程序属性 列表。

前面的部分已经涵盖了许多常见的用例,例如压缩、SSL 或 HTTP/2。然而,如果您的用例没有对应的配置项,那么您应该查看 WebServerFactoryCustomizer。您可以声明这样一个组件,并访问与您选择的服务器相关的工厂:您应该根据所选的服务器(Tomcat、Jetty、Reactor Netty、Undertow)和所选的 Web 技术栈(Servlet 或 Reactive)选择相应的变体。

下面的示例适用于带有 spring-boot-starter-web(servlet 栈)的 Tomcat:

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

@Override
public void customize(TomcatServletWebServerFactory factory) {
// customize the factory here
}

}
java
备注

Spring Boot 在内部使用该基础设施来自动配置服务器。自动配置的 WebServerFactoryCustomizer Bean 的默认顺序为 0,并且会在任何用户定义的自定义器之前处理,除非它明确指定了其他顺序。

一旦你通过自定义器获得了对 WebServerFactory 的访问权限,你就可以使用它来配置特定部分,比如连接器、服务器资源或服务器本身——所有这些都使用特定于服务器的 API。

此外,Spring Boot 还提供了:

作为最后的手段,您还可以声明自己的 WebServerFactory bean,这将覆盖 Spring Boot 提供的默认实现。当您这样做时,自动配置的自定义器仍然会应用于您的自定义工厂,因此请谨慎使用此选项。

向应用程序添加 Servlet、Filter 或 Listener

在一个基于 Servlet 堆栈的应用程序中,即使用 spring-boot-starter-web 时,有两种方法可以将 ServletFilterServletContextListener 以及 Servlet API 支持的其他监听器添加到你的应用程序中:

使用 Spring Bean 添加 Servlet、Filter 或 Listener

要使用 Spring bean 添加 ServletFilter 或 servlet *Listener,你必须为其提供一个 @Bean 定义。当你想要注入配置或依赖时,这样做非常有用。然而,你必须非常小心,确保它们不会导致过早初始化过多的其他 bean,因为它们必须在应用程序生命周期的早期就安装到容器中。(例如,让它们依赖于你的 DataSource 或 JPA 配置并不是一个好主意。)你可以通过在首次使用时延迟初始化这些 bean 来绕过此类限制,而不是在初始化时立即初始化。

在过滤器和 Servlet 的情况下,你还可以通过添加一个 FilterRegistrationBean 或一个 ServletRegistrationBean 来添加映射和初始化参数,而不是或除了底层组件之外。

备注

如果未在过滤器注册中指定 dispatcherType,则使用 REQUEST。这与 Servlet 规范中的默认分发器类型一致。

与其他 Spring bean 一样,您可以定义 Servlet 过滤器 bean 的顺序;请务必查看将 Servlets、Filters 和 Listeners 注册为 Spring Beans 部分。

禁用 Servlet 或 Filter 的注册

正如之前所述,任何 ServletFilter bean 都会自动注册到 servlet 容器中。要禁用特定 FilterServlet bean 的注册,可以为其创建一个注册 bean 并将其标记为禁用,如下例所示:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {

@Bean
public FilterRegistrationBean<MyFilter> registration(MyFilter filter) {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}

}
java

使用类路径扫描添加 Servlets、Filters 和 Listeners

@WebServlet@WebFilter@WebListener 注解的类可以通过在 @Configuration 类上使用 @ServletComponentScan 注解并指定包含要注册组件的包来自动注册到嵌入式 Servlet 容器中。默认情况下,@ServletComponentScan 会从注解类所在的包开始扫描。

配置访问日志

可以通过各自的命名空间为 Tomcat、Undertow 和 Jetty 配置访问日志。

例如,以下设置使用自定义模式记录 Tomcat 的访问日志。

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a %r %s (%D microseconds)
properties
备注

日志的默认位置是相对于 Tomcat 基础目录的 logs 目录。默认情况下,logs 目录是一个临时目录,因此你可能需要固定 Tomcat 的基础目录或为日志使用绝对路径。在前面的示例中,日志位于应用程序工作目录下的 my-tomcat/logs 中。

Undertow 的访问日志可以以类似的方式进行配置,如下例所示:

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a %r %s (%D milliseconds)
server.undertow.options.server.record-request-start-time=true
properties

请注意,除了启用访问日志记录并配置其模式外,还启用了请求开始时间的记录。当在访问日志模式中包含响应时间(%D)时,这是必需的。日志存储在应用程序工作目录下的 logs 目录中。您可以通过设置 server.undertow.accesslog.dir 属性来自定义此位置。

最后,Jetty 的访问日志也可以通过以下方式进行配置:

server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log
properties

默认情况下,日志会被重定向到 System.err。更多详细信息,请参阅 Jetty 文档。

在前端代理服务器后运行

如果你的应用程序运行在代理、负载均衡器或云环境中,请求信息(如主机、端口、协议等)可能会在传输过程中发生变化。你的应用程序可能运行在 10.10.10.10:8080 上,但 HTTP 客户端应该只能看到 example.org

RFC7239 "Forwarded Headers" 定义了 Forwarded HTTP 头;代理可以使用这个头来提供有关原始请求的信息。你可以配置你的应用程序来读取这些头,并在创建链接并将其发送到客户端的 HTTP 302 响应、JSON 文档或 HTML 页面时自动使用这些信息。还有一些非标准的头,比如 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

如果代理服务器添加了常用的 X-Forwarded-ForX-Forwarded-Proto 头部,将 server.forward-headers-strategy 设置为 NATIVE 就足以支持这些头部。通过这个选项,Web 服务器本身原生支持此功能;你可以查看它们的特定文档以了解具体行为。

如果这还不够,Spring Framework 为 Servlet 栈提供了一个 ForwardedHeaderFilter,为响应式栈提供了一个 ForwardedHeaderTransformer。你可以通过在应用程序中将 server.forward-headers-strategy 设置为 FRAMEWORK 来使用它们。

提示

如果你正在使用 Tomcat 并在代理处终止 SSL,应将 server.tomcat.redirect-context-root 设置为 false。这可以在执行任何重定向之前确保 X-Forwarded-Proto 标头得到正确处理。

备注

如果你的应用程序运行在支持的云平台中,server.forward-headers-strategy 属性默认为 NATIVE。在其他所有情况下,它默认为 NONE

自定义 Tomcat 的代理配置

如果你使用 Tomcat,你还可以额外配置用于携带“转发”信息的头字段名称,如下例所示:

server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header
properties

Tomcat 还配置了一个正则表达式,用于匹配可信的内部代理。其默认值请参阅附录中的 server.tomcat.remoteip.internal-proxies 条目。你可以通过向 application.properties 添加条目来自定义 Valve 的配置,如下例所示:

server.tomcat.remoteip.internal-proxies=192\.168\.\d{1,3}\.\d{1,3}
properties
备注

你可以通过将 internal-proxies 设置为空来信任所有代理(但不要在生产环境中这样做)。

你可以通过关闭自动配置(为此,设置 server.forward-headers-strategy=NONE)并使用 WebServerFactoryCustomizer bean 添加一个新的阀门实例来完全控制 Tomcat 的 RemoteIpValve 配置。

在 Tomcat 中启用多连接器

你可以向 TomcatServletWebServerFactory 添加一个 Connector,它可以允许多个连接器,包括 HTTP 和 HTTPS 连接器,如下例所示:

import org.apache.catalina.connector.Connector;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyTomcatConfiguration {

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
return (tomcat) -> tomcat.addAdditionalTomcatConnectors(createConnector());
}

private Connector createConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8081);
return connector;
}

}
java

启用 Tomcat 的 MBean 注册表

默认情况下,嵌入式 Tomcat 的 MBean 注册表是禁用的。这样可以最小化 Tomcat 的内存占用。如果你想使用 Tomcat 的 MBeans,例如让 Micrometer 能够使用它们来暴露指标,你必须使用 server.tomcat.mbeanregistry.enabled 属性来启用它,如下例所示:

server.tomcat.mbeanregistry.enabled=true
properties

使用 Undertow 启用多监听器

UndertowServletWebServerFactory 添加一个 UndertowBuilderCustomizer,并向 io.undertow.Undertow.Builder 添加一个监听器,如下例所示:

import io.undertow.Undertow.Builder;

import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyUndertowConfiguration {

@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowListenerCustomizer() {
return (factory) -> factory.addBuilderCustomizers(this::addHttpListener);
}

private Builder addHttpListener(Builder builder) {
return builder.addHttpListener(8080, "0.0.0.0");
}

}
java

使用 @ServerEndpoint 创建 WebSocket 端点

如果你想在使用了嵌入式容器的 Spring Boot 应用中使用 @ServerEndpoint,你必须声明一个 ServerEndpointExporter @Bean,如下例所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration(proxyBeanMethods = false)
public class MyWebSocketConfiguration {

@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}

}
java

前面示例中展示的 bean 会将任何带有 @ServerEndpoint 注解的 bean 注册到底层的 WebSocket 容器中。当部署到独立的 servlet 容器时,此角色由 servlet 容器的初始化器执行,因此不需要 ServerEndpointExporter bean。