Browse Source

Consider modules when deducing WebApplicationType

Introduce a strategy to `WebApplicationType` to allow modules to
implement deduction logic.

Prior to this commit, modules played no part in deducing the
`WebApplicationType`. This meant that a user with `spring-webflux`
for client purposes would deduce `REACTIVE` despite no
`spring-boot-webflux` module being present.

The following deduction logic order is now implemented:

1) If the `spring-boot-webmvc` module is being used and Spring MVC
   classes are found then `SERVLET` is used.

2) If the `spring-boot-webflux` module is being used and Spring WebFlux
   classes are found then `REACTIVE` is used.

3) If `spring-web` is found and servlet classes are available then
   `SERVLET` is used.

4) If none of the above are satisfied, `NONE` is used.

This commit also updates `SpringBootTestContextBootstrapper` to use
the same deduction logic.

Fixes gh-48517
pull/48543/head
Phillip Webb 2 days ago
parent
commit
da516741b2
  1. 28
      core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java
  2. 2
      core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java
  3. 55
      core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java
  4. 68
      module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/WebFluxWebApplicationTypeDeducer.java
  5. 23
      module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/package-info.java
  6. 3
      module/spring-boot-webflux/src/main/resources/META-INF/spring.factories
  7. 2
      module/spring-boot-webflux/src/main/resources/META-INF/spring/aot.factories
  8. 69
      module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/WebMvcWebApplicationTypeDeducer.java
  9. 23
      module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/package-info.java
  10. 4
      module/spring-boot-webmvc/src/main/resources/META-INF/spring.factories
  11. 2
      module/spring-boot-webmvc/src/main/resources/META-INF/spring/aot.factories
  12. 1
      smoke-test/spring-boot-smoke-test-web-application-type/build.gradle
  13. 92
      smoke-test/spring-boot-smoke-test-web-application-type/src/test/java/smoketest/webapplicationtype/WebApplicationTypeIntegrationTests.java

28
core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java

@ -82,16 +82,6 @@ import org.springframework.util.StringUtils; @@ -82,16 +82,6 @@ import org.springframework.util.StringUtils;
*/
public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstrapper {
private static final String[] WEB_ENVIRONMENT_CLASSES = { "jakarta.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
private static final String ACTIVATE_SERVLET_LISTENER = "org.springframework.test."
+ "context.web.ServletTestExecutionListener.activateListener";
@ -112,7 +102,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr @@ -112,7 +102,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
TestContext context = super.buildTestContext();
verifyConfiguration(context.getTestClass());
WebEnvironment webEnvironment = getWebEnvironment(context.getTestClass());
if (webEnvironment == WebEnvironment.MOCK && deduceWebApplicationType() == WebApplicationType.SERVLET) {
if (webEnvironment == WebEnvironment.MOCK && WebApplicationType.deduce() == WebApplicationType.SERVLET) {
context.setAttribute(ACTIVATE_SERVLET_LISTENER, true);
}
else if (webEnvironment != null && webEnvironment.isEmbedded()) {
@ -171,21 +161,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr @@ -171,21 +161,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
TestPropertySourceUtils.convertInlinedPropertiesToMap(configuration.getPropertySourceProperties()));
Binder binder = new Binder(source);
return binder.bind("spring.main.web-application-type", Bindable.of(WebApplicationType.class))
.orElseGet(this::deduceWebApplicationType);
}
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
.orElseGet(WebApplicationType::deduce);
}
/**

2
core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

@ -275,7 +275,7 @@ public class SpringApplication { @@ -275,7 +275,7 @@ public class SpringApplication {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "'primarySources' must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());
this.properties.setWebApplicationType(WebApplicationType.deduce());
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

55
core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java

@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable; @@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
/**
@ -28,6 +29,7 @@ import org.springframework.util.ClassUtils; @@ -28,6 +29,7 @@ import org.springframework.util.ClassUtils;
*
* @author Andy Wilkinson
* @author Brian Clozel
* @author Phillip Webb
* @since 2.0.0
*/
public enum WebApplicationType {
@ -53,23 +55,28 @@ public enum WebApplicationType { @@ -53,23 +55,28 @@ public enum WebApplicationType {
private static final String[] SERVLET_INDICATOR_CLASSES = { "jakarta.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
/**
* Deduce the {@link WebApplicationType} from the current classpath.
* @return the deduced web application
* @since 4.0.1
*/
public static WebApplicationType deduce() {
for (Deducer deducer : SpringFactoriesLoader.forDefaultResourceLocation().load(Deducer.class)) {
WebApplicationType deduced = deducer.deduceWebApplicationType();
if (deduced != null) {
return deduced;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
return isServletApplication() ? WebApplicationType.SERVLET : WebApplicationType.NONE;
}
private static boolean isServletApplication() {
for (String servletIndicatorClass : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(servletIndicatorClass, null)) {
return false;
}
}
return WebApplicationType.SERVLET;
return true;
}
static class WebApplicationTypeRuntimeHints implements RuntimeHintsRegistrar {
@ -79,9 +86,6 @@ public enum WebApplicationType { @@ -79,9 +86,6 @@ public enum WebApplicationType {
for (String servletIndicatorClass : SERVLET_INDICATOR_CLASSES) {
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
}
registerTypeIfPresent(JERSEY_INDICATOR_CLASS, classLoader, hints);
registerTypeIfPresent(WEBFLUX_INDICATOR_CLASS, classLoader, hints);
registerTypeIfPresent(WEBMVC_INDICATOR_CLASS, classLoader, hints);
}
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
@ -92,4 +96,21 @@ public enum WebApplicationType { @@ -92,4 +96,21 @@ public enum WebApplicationType {
}
/**
* Strategy that may be implemented by a module that can deduce the
* {@link WebApplicationType}.
*
* @since 4.0.1
*/
@FunctionalInterface
public interface Deducer {
/**
* Deduce the web application type.
* @return the deduced web application type or {@code null}
*/
@Nullable WebApplicationType deduceWebApplicationType();
}
}

68
module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/WebFluxWebApplicationTypeDeducer.java

@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
/*
* Copyright 2012-present 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.webflux;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.boot.WebApplicationType;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
/**
* {@link WebApplicationType} deducer to ensure Spring WebFlux applications use
* {@link WebApplicationType#REACTIVE}.
*
* @author Phillip Webb
*/
@Order(20) // Ordered after MVC
class WebFluxWebApplicationTypeDeducer implements WebApplicationType.Deducer {
private static final String[] INDICATOR_CLASSES = { "reactor.core.publisher.Mono",
"org.springframework.web.reactive.DispatcherHandler" };
@Override
public @Nullable WebApplicationType deduceWebApplicationType() {
// Guard in case the classic module is being used and dependencies are excluded
for (String indicatorClass : INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(indicatorClass, null)) {
return null;
}
}
return WebApplicationType.REACTIVE;
}
static class WebFluxWebApplicationTypeDeducerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
for (String servletIndicatorClass : INDICATOR_CLASSES) {
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
}
}
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
if (ClassUtils.isPresent(typeName, classLoader)) {
hints.reflection().registerType(TypeReference.of(typeName));
}
}
}
}

23
module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/package-info.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* Copyright 2012-present 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.
*/
/**
* Core integration between Spring Boot and Spring WebFlux.
*/
@NullMarked
package org.springframework.boot.webflux;
import org.jspecify.annotations.NullMarked;

3
module/spring-boot-webflux/src/main/resources/META-INF/spring.factories

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
# WebApplicationType Deducers
org.springframework.boot.WebApplicationType$Deducer=\
org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer

2
module/spring-boot-webflux/src/main/resources/META-INF/spring/aot.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer$WebFluxWebApplicationTypeDeducerRuntimeHints

69
module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/WebMvcWebApplicationTypeDeducer.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2012-present 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.webmvc;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.boot.WebApplicationType;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
/**
* {@link WebApplicationType} deducer to ensure Spring MVC applications use
* {@link WebApplicationType#SERVLET}.
*
* @author Phillip Webb
*/
@Order(10) // Ordered after WebFlux
class WebMvcWebApplicationTypeDeducer implements WebApplicationType.Deducer {
private static final String[] INDICATOR_CLASSES = { "jakarta.servlet.Servlet",
"org.springframework.web.servlet.DispatcherServlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
@Override
public @Nullable WebApplicationType deduceWebApplicationType() {
// Guard in case the classic module is being used and dependencies are excluded
for (String indicatorClass : INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(indicatorClass, null)) {
return null;
}
}
return WebApplicationType.SERVLET;
}
static class WebMvcWebApplicationTypeDeducerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
for (String servletIndicatorClass : INDICATOR_CLASSES) {
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
}
}
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
if (ClassUtils.isPresent(typeName, classLoader)) {
hints.reflection().registerType(TypeReference.of(typeName));
}
}
}
}

23
module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/package-info.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* Copyright 2012-present 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.
*/
/**
* Core integration between Spring Boot and Spring Web MVC.
*/
@NullMarked
package org.springframework.boot.webmvc;
import org.jspecify.annotations.NullMarked;

4
module/spring-boot-webmvc/src/main/resources/META-INF/spring.factories

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
# Template Availability Providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider
# WebApplicationType Deducers
org.springframework.boot.WebApplicationType$Deducer=\
org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer

2
module/spring-boot-webmvc/src/main/resources/META-INF/spring/aot.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer$WebMvcWebApplicationTypeDeducerRuntimeHints

1
smoke-test/spring-boot-smoke-test-web-application-type/build.gradle

@ -25,6 +25,7 @@ dependencies { @@ -25,6 +25,7 @@ dependencies {
implementation(project(":starter:spring-boot-starter-webflux"))
testImplementation(project(":starter:spring-boot-starter-test"))
testImplementation(project(":test-support:spring-boot-test-support"))
}
tasks.named("compileTestJava") {

92
smoke-test/spring-boot-smoke-test-web-application-type/src/test/java/smoketest/webapplicationtype/WebApplicationTypeIntegrationTests.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
/*
* Copyright 2012-present 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 smoketest.webapplicationtype;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link WebApplicationType} deducers.
*
* @author Phillip Webb
*/
class WebApplicationTypeIntegrationTests {
@Test
@ClassPathExclusions("spring-boot-webflux*")
void deduceWhenNoWebFluxModuleAndWebMvcModule() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.SERVLET);
}
@Test
@ClassPathExclusions({ "spring-boot-webflux*", "spring-web-*" })
void deduceWhenNoWebFluxModuleAndNoSpringWebAndWebMvcModule() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.NONE);
}
@Test
@ClassPathExclusions("spring-boot-webmvc*")
void deduceWhenNoWebMvcModuleAndWebFluxModule() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.REACTIVE);
}
@Test
@ClassPathExclusions({ "spring-boot-webmvc*", "spring-webflux-*" })
void deduceWhenNoWebMvcModuleAndNoWebFluxAndWebFluxModule() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.SERVLET);
}
@Test
@ClassPathExclusions({ "spring-boot-webmvc*", "spring-webflux-*", "spring-web-*" })
void deduceWhenNoWebMvcModuleAndNoWebFluxAndNoWebAndWebFluxModule() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.NONE);
}
@Test
void deduceWhenWebMvcAndWebFlux() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.SERVLET);
}
@Test
@ClassPathExclusions("spring-webmvc-*")
void deduceWhenNoWebMvc() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.REACTIVE);
}
@Test
@ClassPathExclusions({ "spring-webmvc-*", "spring-webflux-*" })
void deduceWhenNoWebMvcAndNoWebFlux() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.SERVLET);
}
@Test
@ClassPathExclusions({ "spring-web-*" })
void deduceWhenNoWeb() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.NONE);
}
@Test
@ClassPathExclusions(packages = "jakarta.servlet", files = "spring-webflux-*")
void deduceWhenNoServletOrWebFlux() {
assertThat(WebApplicationType.deduce()).isEqualTo(WebApplicationType.NONE);
}
}
Loading…
Cancel
Save