Create spring-boot-web-server module

This commit is contained in:
Andy Wilkinson 2025-05-01 13:40:49 +01:00
parent 61ed41b106
commit f32f0f841c
203 changed files with 375 additions and 281 deletions

View File

@ -142,6 +142,7 @@ include "spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"
include "spring-boot-project:spring-boot-tx"
include "spring-boot-project:spring-boot-undertow"
include "spring-boot-project:spring-boot-validation"
include "spring-boot-project:spring-boot-web-server"
include "spring-boot-project:spring-boot-webflux"
include "spring-boot-project:spring-boot-webmvc"
include "spring-boot-project:spring-boot-webservices"

View File

@ -178,7 +178,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-hateoas"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation("io.micrometer:micrometer-observation-test")
testImplementation("io.opentelemetry:opentelemetry-exporter-common")
testImplementation("io.projectreactor:reactor-test")

View File

@ -74,7 +74,6 @@ dependencies {
}
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation("com.ibm.db2:jcc")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test")

View File

@ -161,7 +161,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-undertow"))
testImplementation(project(":spring-boot-project:spring-boot-mustache"))
testImplementation(project(":spring-boot-project:spring-boot-thymeleaf"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("ch.qos.logback:logback-classic")
testImplementation("commons-fileupload:commons-fileupload")

View File

@ -11,14 +11,13 @@ description = "Spring Boot AutoConfigure"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-web-server"))
optional("org.aspectj:aspectjweaver")
optional("jakarta.persistence:jakarta.persistence-api")
optional("jakarta.servlet:jakarta.servlet-api")
optional("javax.money:money-api")
optional("org.springframework:spring-web")
optional("org.springframework:spring-web")
optional("org.springframework:spring-web")
optional("org.springframework.data:spring-data-commons")
testFixturesCompileOnly(project(":spring-boot-project:spring-boot-test"))
@ -27,12 +26,12 @@ dependencies {
testFixturesCompileOnly("jakarta.websocket:jakarta.websocket-api")
testFixturesCompileOnly("jakarta.websocket:jakarta.websocket-client-api")
testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation("ch.qos.logback:logback-classic")
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("io.projectreactor:reactor-core")

View File

@ -2152,6 +2152,7 @@ bom {
"spring-boot-tx",
"spring-boot-undertow",
"spring-boot-validation",
"spring-boot-web-server",
"spring-boot-webflux",
"spring-boot-webmvc",
"spring-boot-websocket"

View File

@ -9,7 +9,7 @@ plugins {
description = "Spring Boot Jetty"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-web-server"))
api("org.eclipse.jetty.ee10:jetty-ee10-servlets")
api("org.eclipse.jetty.ee10:jetty-ee10-webapp")
@ -23,7 +23,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("org.apache.httpcomponents.client5:httpclient5")
testImplementation("org.assertj:assertj-core")

View File

@ -9,7 +9,7 @@ plugins {
description = "Spring Boot Reactor Netty"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-web-server"))
api("io.projectreactor.netty:reactor-netty-http")
api("org.springframework:spring-web")
@ -19,7 +19,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.assertj:assertj-core")

View File

@ -17,7 +17,7 @@ dependencies {
dockerTestImplementation(project(":spring-boot-project:spring-boot-test"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-webflux"))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
dockerTestImplementation("org.assertj:assertj-core")
dockerTestImplementation("org.junit.jupiter:junit-jupiter")

View File

@ -17,7 +17,7 @@ dependencies {
dockerTestImplementation(project(":spring-boot-project:spring-boot-test"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-webflux"))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
dockerTestImplementation("com.redis:testcontainers-redis")
dockerTestImplementation("org.assertj:assertj-core")
@ -35,5 +35,5 @@ dependencies {
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("org.springframework:spring-web")
testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@ -19,15 +19,15 @@ dependencies {
testFixturesImplementation(project(":spring-boot-project:spring-boot-reactor"))
testFixturesImplementation(project(":spring-boot-project:spring-boot-test"))
testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tomcat"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-webflux"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@ -12,6 +12,7 @@ dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.springframework:spring-test")
optional(project(":spring-boot-project:spring-boot-web-server"))
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("com.jayway.jsonpath:json-path")

View File

@ -31,7 +31,6 @@ import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.web.context.reactive.ReactiveWebApplicationContext;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
@ -155,8 +154,8 @@ public @interface SpringBootTest {
RANDOM_PORT(true),
/**
* Creates a (reactive) web application context without defining any
* {@code server.port=0} {@link Environment} property.
* Creates a web application context (reactive or servlet based) without defining
* any {@code server.port=0} {@link Environment} property.
*/
DEFINED_PORT(true),
@ -174,8 +173,8 @@ public @interface SpringBootTest {
}
/**
* Return if the environment uses an {@link ServletWebServerApplicationContext}.
* @return if an {@link ServletWebServerApplicationContext} is used.
* Return if the environment uses an embedded web server.
* @return if an embedded web server is used
*/
public boolean isEmbedded() {
return this.embedded;

View File

@ -15,7 +15,7 @@ configurations {
}
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-web-server"))
api("org.apache.tomcat.embed:tomcat-embed-core") {
exclude group: "org.apache.tomcat", module: "tomcat-annotations-api"
}
@ -31,7 +31,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("org.apache.httpcomponents.client5:httpclient5")

View File

@ -40,7 +40,7 @@ class TransactionManagerCustomizationAutoConfigurationTests {
@Test
void autoConfiguresTransactionManagerCustomizers() {
this.contextRunner.run((context) -> {
this.contextRunner.withPropertyValues("spring.transaction.default-timeout=30s").run((context) -> {
TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class);
assertThat(customizers).extracting("customizers")
.asInstanceOf(InstanceOfAssertFactories.LIST)

View File

@ -9,7 +9,7 @@ plugins {
description = "Spring Boot Undertow"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-web-server"))
api("io.undertow:undertow-servlet")
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
@ -18,7 +18,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-web-server")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("org.apache.httpcomponents.client5:httpclient5")
testImplementation("org.apache.tomcat.embed:tomcat-embed-jasper")

View File

@ -0,0 +1,36 @@
plugins {
id "java-library"
id "java-test-fixtures"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Web Server"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.springframework:spring-web")
optional("io.projectreactor:reactor-core")
optional("jakarta.servlet:jakarta.servlet-api")
optional("org.springframework:spring-test")
testFixturesCompileOnly(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testFixturesCompileOnly("io.projectreactor:reactor-test")
testFixturesCompileOnly("io.projectreactor.netty:reactor-netty-http")
testFixturesCompileOnly("org.apache.httpcomponents.client5:httpclient5")
testFixturesCompileOnly("org.apache.tomcat.embed:tomcat-embed-jasper")
testFixturesCompileOnly("org.eclipse.jetty.http2:jetty-http2-client")
testFixturesCompileOnly("org.eclipse.jetty.http2:jetty-http2-client-transport")
testFixturesCompileOnly("jakarta.servlet:jakarta.servlet-api")
testFixturesCompileOnly("org.mockito:mockito-core")
testFixturesCompileOnly("org.springframework:spring-tx")
testFixturesCompileOnly("org.springframework:spring-webflux")
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation("org.apache.tomcat.embed:tomcat-embed-core")
testImplementation("org.springframework:spring-webmvc")
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@ -0,0 +1,13 @@
# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextFactory,\
org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextFactory
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.web.server.PortInUseFailureAnalyzer,\
org.springframework.boot.web.server.context.MissingWebServerFactoryBeanFailureAnalyzer

View File

@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.web.server.MimeMappings$MimeMappingsRuntimeHints

View File

@ -0,0 +1,188 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.server;
import java.util.Iterator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.web.context.reactive.ReactiveWebApplicationContext;
import org.springframework.boot.web.context.reactive.StandardReactiveWebEnvironment;
import org.springframework.boot.web.server.reactive.MockReactiveWebServerFactory;
import org.springframework.boot.web.server.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.server.servlet.MockServletWebServerFactory;
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.PropertySource;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringApplication} with a {@link WebServer}.
*
* @author Andy wilkinson
*/
class SpringApplicationWebServerTests {
private String headlessProperty;
private ConfigurableApplicationContext context;
@BeforeEach
void storeAndClearHeadlessProperty() {
this.headlessProperty = System.getProperty("java.awt.headless");
System.clearProperty("java.awt.headless");
}
@AfterEach
void reinstateHeadlessProperty() {
if (this.headlessProperty == null) {
System.clearProperty("java.awt.headless");
}
else {
System.setProperty("java.awt.headless", this.headlessProperty);
}
}
@AfterEach
void cleanUp() {
if (this.context != null) {
this.context.close();
}
System.clearProperty("spring.main.banner-mode");
}
@Test
void defaultApplicationContextForWeb() {
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
application.setWebApplicationType(WebApplicationType.SERVLET);
this.context = application.run();
assertThat(this.context).isInstanceOf(AnnotationConfigServletWebServerApplicationContext.class);
}
@Test
void defaultApplicationContextForReactiveWeb() {
SpringApplication application = new SpringApplication(ExampleReactiveWebConfig.class);
application.setWebApplicationType(WebApplicationType.REACTIVE);
this.context = application.run();
assertThat(this.context).isInstanceOf(AnnotationConfigReactiveWebServerApplicationContext.class);
}
@Test
void environmentForWeb() {
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
application.setWebApplicationType(WebApplicationType.SERVLET);
this.context = application.run();
assertThat(this.context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class);
assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
}
@Test
void environmentForReactiveWeb() {
SpringApplication application = new SpringApplication(ExampleReactiveWebConfig.class);
application.setWebApplicationType(WebApplicationType.REACTIVE);
this.context = application.run();
assertThat(this.context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
}
@Test
void webApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironment() {
ConfigurableApplicationContext context = new SpringApplication(ExampleWebConfig.class)
.run("--spring.main.web-application-type=servlet");
assertThat(context).isInstanceOf(WebApplicationContext.class);
assertThat(context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class);
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
}
@Test
void reactiveApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironment() {
ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class)
.run("--spring.main.web-application-type=reactive");
assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class);
assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
}
@Test
@WithResource(name = "application-withwebapplicationtype.properties",
content = "spring.main.web-application-type=reactive")
void environmentIsConvertedIfTypeDoesNotMatch() {
ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class)
.run("--spring.profiles.active=withwebapplicationtype");
assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class);
assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
}
@Test
void webApplicationSwitchedOffInListener() {
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
application.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) (event) -> {
assertThat(event.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(event.getEnvironment(), "foo=bar");
event.getSpringApplication().setWebApplicationType(WebApplicationType.NONE);
});
this.context = application.run();
assertThat(this.context.getEnvironment()).isNotInstanceOf(StandardServletEnvironment.class);
assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar");
Iterator<PropertySource<?>> iterator = this.context.getEnvironment().getPropertySources().iterator();
assertThat(iterator.next().getName()).isEqualTo("configurationProperties");
assertThat(iterator.next().getName())
.isEqualTo(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
}
@Configuration(proxyBeanMethods = false)
static class ExampleWebConfig {
@Bean
MockServletWebServerFactory webServer() {
return new MockServletWebServerFactory();
}
}
@Configuration(proxyBeanMethods = false)
static class ExampleReactiveWebConfig {
@Bean
MockReactiveWebServerFactory webServerFactory() {
return new MockReactiveWebServerFactory();
}
@Bean
HttpHandler httpHandler() {
return (serverHttpRequest, serverHttpResponse) -> Mono.empty();
}
}
}

Some files were not shown because too many files have changed in this diff Show More