73. 嵌入式 Servlet 容器

73.1 将 Servlet,过滤器或侦听器添加到应用程序

有两种方法可将 Servlet 规范支持的ServletFilterServletContextListener和其他侦听器添加到您的应用程序。您可以为它们提供 Spring Bean,或者启用对 Servlet 组件的扫描。

73.1.1 使用 Spring bean 添加 Servlet,Filter 或 Listener

要添加ServletFilter或 Servlet *Listener,请为其提供@Bean定义。当您要注入配置或依赖项时,这可能非常有用。但是,您必须非常小心,以免引起过多其他 bean 的急切初始化,因为必须在应用程序生命周期的早期就将它们安装在容器中(例如,让它们取决于DataSource或 JPA 配置)。您可以通过在第一次使用时(而不是在初始化时)延迟初始化它们来解决类似的限制。

FiltersServlets的情况下,您还可以通过添加FilterRegistrationBeanServletRegistrationBean而不是基础组件或添加基础组件来添加 Map 和 init 参数。

Note

如果在过滤器注册上未指定dispatcherType,它将匹配FORWARDINCLUDEREQUEST。如果已启用异步,它将也匹配ASYNC

如果要迁移在web.xml中没有dispatcher元素的过滤器,则需要自己指定dispatcherType

@Bean
public FilterRegistrationBean myFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
....

return registration;
}

禁用 Servlet 或过滤器的注册

作为described above,任何ServletFilter bean 都将自动注册到 servlet 容器中。要禁用特定FilterServlet bean 的注册,请为其创建注册 bean 并将其标记为已禁用。例如:

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

73.1.2 使用 Classpath 扫描添加 Servlet,过滤器和侦听器

通过使用@ServletComponentScanComments@Configuration类并指定包含要注册的组件的软件包,可以自动将@WebServlet@WebFilter@WebListenerComments 的类注册到嵌入式 servlet 容器中。默认情况下,@ServletComponentScan将从带 Comments 的类的包中进行扫描。

73.2 更改 HTTP 端口

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

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

有关更多详细信息,请参阅“ Spring Boot 功能”部分中的* 第 27.3.4 节“自定义嵌入式 servlet 容器” *或ServerProperties源代码。

73.3 使用随机未分配的 HTTP 端口

要扫描可用端口(使用 os 本机来防止冲突),请使用server.port=0

73.4 在运行时发现 HTTP 端口

您可以从日志输出或EmbeddedWebApplicationContext通过其EmbeddedServletContainer访问服务器正在运行的端口。最好的方法是确保它已初始化,是添加类型为ApplicationListener<EmbeddedServletContainerInitializedEvent>@Bean,并在事件发布时将其拉出事件。

使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)的测试还可以使用@LocalServerPort注解将实际端口注入字段。例如:

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

    @Autowired
    EmbeddedWebApplicationContext server;

    @LocalServerPort
    int port;

    // ...

}

Note

@LocalServerPort@Value("${local.server.port}")的元 Comments。不要尝试在常规应用程序中注入端口。正如我们所看到的,只有在容器初始化后才设置该值。与测试相反,应用程序代码回调将在早期(即在该值实际可用之前)进行处理。

73.5 配置 SSL

可以通过设置各种server.ssl.*属性(通常在application.propertiesapplication.yml中)来声明性地配置 SSL。例如:

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

有关所有受支持属性的详细信息,请参见Ssl

像上面的例子一样使用配置意味着应用程序将不再在端口 8080 上支持纯 HTTP 连接器。SpringBoot 不支持通过application.properties配置 HTTP 连接器和 HTTPS 连接器。如果要同时拥有两者,则需要以编程方式配置其中之一。建议使用application.properties来配置 HTTPS,因为 HTTP 连接器是两者中以编程方式配置时比较容易的一种。有关示例,请参见spring-boot-sample-tomcat-multi-connectors示例项目。

73.6 配置访问日志

可以通过它们各自的名称空间为 Tomcat 和 Undertow 配置访问日志。

例如,以下日志使用custom pattern记录对 Tomcat 的访问。

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)

Note

日志的默认位置是相对于 tomcat 基本目录的logs目录,默认情况下该目录是临时目录,因此您可能需要修复 Tomcat 的基本目录或使用日志的绝对路径。在上面的示例中,相对于应用程序的工作目录,日志将在my-tomcat/logs中可用。

可以类似的方式配置 undertow 的访问日志

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a "%r" %s (%D ms)

日志存储在相对于应用程序工作目录的logs目录中。可以通过server.undertow.accesslog.directory进行自定义。

73.7 在前端代理服务器后面使用

您的应用程序可能需要发送302重定向或使用绝对链接将内容渲染回自身。在代理后面运行时,调用者需要链接到代理,而不是指向托管您的应用程序的计算机的物理地址。通常,这种情况是通过与代理之间的 Contract 来处理的,代理会添加 Headers 来告诉后端如何构造到自身的链接。

如果代理添加了常规的X-Forwarded-ForX-Forwarded-ProtoHeaders(大多数操作都是开箱即用的),则只要将application.properties中的server.use-forward-headers设置为true,就应该正确渲染绝对链接。

Note

如果您的应用程序在 Cloud Foundry 或 Heroku 中运行,则如果未指定,则server.use-forward-headers属性将默认为true。在所有其他情况下,它默认为false

73.7.1 自定义 Tomcat 的代理配置

如果使用的是 Tomcat,则可以另外配置用于承载“转发的”信息的 Headers 名称:

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

Tomcat 还配置有默认正则表达式,该正则表达式与要信任的内部代理匹配。默认情况下,10/8192.168/16169.254/16127/8中的 IP 地址是受信任的。您可以通过在application.properties中添加一个条目来自定义阀门的配置,例如

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

Note

仅在使用属性文件进行配置时才需要双反斜杠。如果您使用的是 YAML,则单个反斜杠就足够了,与上面显示的值相等的值为192\.168\.\d{1,3}\.\d{1,3}

Note

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

您可以通过关闭自动功能(即设置server.use-forward-headers=false)并在TomcatEmbeddedServletContainerFactory bean 中添加新的 Valve 实例来完全控制 Tomcat RemoteIpValve的配置。

73.8 配置 Tomcat

通常,您可以遵循* 第 72.8 节“发现外部属性的内置选项” *关于@ConfigurationProperties的建议(这里主要是ServerProperties),但也可以查看EmbeddedServletContainerCustomizer和各种特定于 Tomcat 的*Customizers,您可以在其中之一中添加。 Tomcat API 非常丰富,因此一旦您可以访问TomcatEmbeddedServletContainerFactory,就可以通过多种方式对其进行修改。或核选择是添加自己的TomcatEmbeddedServletContainerFactory

73.9 使用 Tomcat 启用多个连接器

org.apache.catalina.connector.Connector添加到TomcatEmbeddedServletContainerFactory可以允许多个连接器,例如 HTTP 和 HTTPS 连接器:

@Bean
public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createSslConnector());
    return tomcat;
}

private Connector createSslConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    try {
        File keystore = new ClassPathResource("keystore").getFile();
        File truststore = new ClassPathResource("keystore").getFile();
        connector.setScheme("https");
        connector.setSecure(true);
        connector.setPort(8443);
        protocol.setSSLEnabled(true);
        protocol.setKeystoreFile(keystore.getAbsolutePath());
        protocol.setKeystorePass("changeit");
        protocol.setTruststoreFile(truststore.getAbsolutePath());
        protocol.setTruststorePass("changeit");
        protocol.setKeyAlias("apitester");
        return connector;
    }
    catch (IOException ex) {
        throw new IllegalStateException("can't access keystore: [" + "keystore"
                + "] or truststore: [" + "keystore" + "]", ex);
    }
}

73.10 使用 Tomcat 的 LegacyCookieProcessor

Spring Boot 使用的嵌入式 Tomcat 不支持开箱即用的 Cookie 格式的“版本 0”,并且您可能会看到以下错误:

java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value

如果有可能,您应该考虑将代码更新为仅存储符合以后 Cookie 规范的值。但是,如果您无法更改 cookie 的编写方式,则可以将 Tomcat 配置为使用LegacyCookieProcessor。要切换到LegacyCookieProcessor,请使用添加TomcatContextCustomizerEmbeddedServletContainerCustomizer bean:

@Bean
public EmbeddedServletContainerCustomizer cookieProcessorCustomizer() {
    return new EmbeddedServletContainerCustomizer() {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                ((TomcatEmbeddedServletContainerFactory) container)
                        .addContextCustomizers(new TomcatContextCustomizer() {

                    @Override
                    public void customize(Context context) {
                        context.setCookieProcessor(new LegacyCookieProcessor());
                    }

                });
            }
        }

    };
}

73.11 使用 Jetty 代替 Tomcat

Spring Boot 启动器(尤其是spring-boot-starter-web)默认情况下将 Tomcat 用作嵌入式容器。您需要排除这些依赖项,而改为包含 Jetty。 Spring Boot 提供了 Binding 在一起的 Tomcat 和 Jetty 依赖关系,作为独立的启动程序,以帮助简化此过程。

Maven 中的示例:

<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-jetty</artifactId>
</dependency>

Gradle 中的示例:

configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.5.9.RELEASE")
    compile("org.springframework.boot:spring-boot-starter-jetty:1.5.9.RELEASE")
    // ...
}

73.12 配置 Jetty

通常,您可以遵循* 第 72.8 节“发现外部属性的内置选项” *关于@ConfigurationProperties的建议(这里主要是ServerProperties),但也可以参考EmbeddedServletContainerCustomizer。 Jetty API 非常丰富,因此一旦您可以访问JettyEmbeddedServletContainerFactory,就可以通过多种方式对其进行修改。或核选择是添加自己的JettyEmbeddedServletContainerFactory

73.13 使用 Undertow 代替 Tomcat

使用 Undertow 代替 Tomcat 与使用 Jetty 代替 Tomcat非常相似。您需要排除 Tomcat 依赖项,而应包括 UndertowStarter 程序。

Maven 中的示例:

<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>

Gradle 中的示例:

configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.5.9.RELEASE")
    compile("org.springframework.boot:spring-boot-starter-undertow:1.5.9.RELEASE")
    // ...
}

73.14 配置 Underwow

通常,您可以遵循* 第 72.8 节“发现外部属性的内置选项” *关于@ConfigurationProperties的建议(这里主要是ServerPropertiesServerProperties.Undertow),但也可以查看EmbeddedServletContainerCustomizer。一旦可以访问UndertowEmbeddedServletContainerFactory,就可以使用UndertowBuilderCustomizer修改 Undertow 的配置以满足您的需求。或核选择是添加自己的UndertowEmbeddedServletContainerFactory

73.15 使用 Undertow 启用多个侦听器

UndertowBuilderCustomizer添加到UndertowEmbeddedServletContainerFactory并将侦听器添加到Builder

@Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
    UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
    factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

        @Override
        public void customize(Builder builder) {
            builder.addHttpListener(8080, "0.0.0.0");
        }

    });
    return factory;
}

73.16 使用 Tomcat 7.x 或 8.0

Tomcat 7 和 8.0 可与 Spring Boot 一起使用,但默认设置是使用 Tomcat 8.5. 如果您不能使用 Tomcat 8.5(例如,因为使用 Java 1.6),则需要更改 Classpath 以引用其他版本。

73.16.1 将 Tomcat 7.x 或 8.0 与 Maven 结合使用

如果您使用的是 Starters 和 parent,则可以更改 Tomcat 版本属性并另外导入tomcat-juli。例如。对于简单的 Web 应用程序或服务:

<properties>
    <tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencies>
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-juli</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    ...
</dependencies>

73.16.2 将 Tomcat 7.x 或 8.0 与 Gradle 一起使用

使用 Gradle,您可以通过设置tomcat.version属性来更改 Tomcat 版本,然后另外包含tomcat-juli

ext['tomcat.version'] = '7.0.59'
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile group:'org.apache.tomcat', name:'tomcat-juli', version:property('tomcat.version')
}

73.17 使用 Jetty9.2

Jetty 9.2 与 Spring Boot 一起使用,但默认设置是使用 Jetty 9.3. 如果您不能使用 Jetty 9.3(例如,因为您使用的是 Java 7),则需要将 Classpath 更改为引用 Jetty 9.2.

73.17.1 在 Maven 中使用 Jetty 9.2

如果您使用启动器和父级,则只需添加 Jetty 启动器并覆盖jetty.version属性:

<properties>
    <jetty.version>9.2.17.v20160517</jetty.version>
</properties>
<dependencies>
    <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-jetty</artifactId>
    </dependency>
</dependencies>

73.17.2 将 Jetty 9.2 与 Gradle 一起使用

您可以设置jetty.version属性。例如,对于简单的 Web 应用程序或服务:

ext['jetty.version'] = '9.2.17.v20160517'
dependencies {
    compile ('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    compile ('org.springframework.boot:spring-boot-starter-jetty')
}

73.18 使用 Jetty8

Jetty 8 可与 Spring Boot 一起使用,但默认值为使用 Jetty 9.3. 如果您不能使用 Jetty 9.3(例如,因为使用 Java 1.6),则需要将 Classpath 更改为引用 Jetty8.您还需要排除与 Jetty 的 WebSocket 相关的依赖。

73.18.1 将 Jetty 8 与 Maven 结合使用

如果您使用的是 Starters 和 parent,则只需添加具有所需 WebSocket 排除项的 JettyStarters 并更改版本属性,例如对于简单的 Web 应用程序或服务:

<properties>
    <jetty.version>8.1.15.v20140411</jetty.version>
    <jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
</properties>
<dependencies>
    <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-jetty</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.eclipse.jetty.websocket</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

73.18.2 将 Jetty 8 与 Gradle 一起使用

您可以设置jetty.version属性,并排除 WebSocket 依赖项,例如对于简单的 Web 应用程序或服务:

ext['jetty.version'] = '8.1.15.v20140411'
dependencies {
    compile ('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    compile ('org.springframework.boot:spring-boot-starter-jetty') {
        exclude group: 'org.eclipse.jetty.websocket'
    }
}

73.19 使用@ServerEndpoint 创建 WebSocket 端点

如果要在使用嵌入式容器的 Spring Boot 应用程序中使用@ServerEndpoint,则必须声明一个ServerEndpointExporter @Bean

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

该 bean 将在基础 WebSocket 容器中注册任何带有@ServerEndpointComments 的 bean。当部署到独立的 servlet 容器中时,此角色由 servlet 容器初始化程序执行,并且不需要ServerEndpointExporter bean。

73.20 启用 HTTP 响应压缩

Jetty,Tomcat 和 Undertow 支持 HTTP 响应压缩。可以通过application.properties启用它:

server.compression.enabled=true

默认情况下,响应的长度必须至少为 2048 个字节才能执行压缩。可以使用server.compression.min-response-size属性进行配置。

默认情况下,仅当响应的 Content Type 为以下之一时,才会压缩它们:

  • text/html

  • text/xml

  • text/plain

  • text/css

可以使用server.compression.mime-types属性进行配置。