Skip to content

Commit da51674

Browse files
committed
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
1 parent f9fa134 commit da51674

File tree

13 files changed

+328
-44
lines changed

13 files changed

+328
-44
lines changed

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

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,6 @@
8282
*/
8383
public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstrapper {
8484

85-
private static final String[] WEB_ENVIRONMENT_CLASSES = { "jakarta.servlet.Servlet",
86-
"org.springframework.web.context.ConfigurableWebApplicationContext" };
87-
88-
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
89-
+ "web.reactive.DispatcherHandler";
90-
91-
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework.web.servlet.DispatcherServlet";
92-
93-
private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
94-
9585
private static final String ACTIVATE_SERVLET_LISTENER = "org.springframework.test."
9686
+ "context.web.ServletTestExecutionListener.activateListener";
9787

@@ -112,7 +102,7 @@ public TestContext buildTestContext() {
112102
TestContext context = super.buildTestContext();
113103
verifyConfiguration(context.getTestClass());
114104
WebEnvironment webEnvironment = getWebEnvironment(context.getTestClass());
115-
if (webEnvironment == WebEnvironment.MOCK && deduceWebApplicationType() == WebApplicationType.SERVLET) {
105+
if (webEnvironment == WebEnvironment.MOCK && WebApplicationType.deduce() == WebApplicationType.SERVLET) {
116106
context.setAttribute(ACTIVATE_SERVLET_LISTENER, true);
117107
}
118108
else if (webEnvironment != null && webEnvironment.isEmbedded()) {
@@ -171,21 +161,7 @@ private WebApplicationType getWebApplicationType(MergedContextConfiguration conf
171161
TestPropertySourceUtils.convertInlinedPropertiesToMap(configuration.getPropertySourceProperties()));
172162
Binder binder = new Binder(source);
173163
return binder.bind("spring.main.web-application-type", Bindable.of(WebApplicationType.class))
174-
.orElseGet(this::deduceWebApplicationType);
175-
}
176-
177-
private WebApplicationType deduceWebApplicationType() {
178-
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
179-
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
180-
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
181-
return WebApplicationType.REACTIVE;
182-
}
183-
for (String className : WEB_ENVIRONMENT_CLASSES) {
184-
if (!ClassUtils.isPresent(className, null)) {
185-
return WebApplicationType.NONE;
186-
}
187-
}
188-
return WebApplicationType.SERVLET;
164+
.orElseGet(WebApplicationType::deduce);
189165
}
190166

191167
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ public SpringApplication(@Nullable ResourceLoader resourceLoader, Class<?>... pr
275275
this.resourceLoader = resourceLoader;
276276
Assert.notNull(primarySources, "'primarySources' must not be null");
277277
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
278-
this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());
278+
this.properties.setWebApplicationType(WebApplicationType.deduce());
279279
this.bootstrapRegistryInitializers = new ArrayList<>(
280280
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
281281
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

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

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
import org.springframework.aot.hint.RuntimeHints;
2222
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2323
import org.springframework.aot.hint.TypeReference;
24+
import org.springframework.core.io.support.SpringFactoriesLoader;
2425
import org.springframework.util.ClassUtils;
2526

2627
/**
2728
* An enumeration of possible types of web application.
2829
*
2930
* @author Andy Wilkinson
3031
* @author Brian Clozel
32+
* @author Phillip Webb
3133
* @since 2.0.0
3234
*/
3335
public enum WebApplicationType {
@@ -53,23 +55,28 @@ public enum WebApplicationType {
5355
private static final String[] SERVLET_INDICATOR_CLASSES = { "jakarta.servlet.Servlet",
5456
"org.springframework.web.context.ConfigurableWebApplicationContext" };
5557

56-
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
57-
58-
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
59-
60-
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
61-
62-
static WebApplicationType deduceFromClasspath() {
63-
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
64-
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
65-
return WebApplicationType.REACTIVE;
58+
/**
59+
* Deduce the {@link WebApplicationType} from the current classpath.
60+
* @return the deduced web application
61+
* @since 4.0.1
62+
*/
63+
public static WebApplicationType deduce() {
64+
for (Deducer deducer : SpringFactoriesLoader.forDefaultResourceLocation().load(Deducer.class)) {
65+
WebApplicationType deduced = deducer.deduceWebApplicationType();
66+
if (deduced != null) {
67+
return deduced;
68+
}
6669
}
67-
for (String className : SERVLET_INDICATOR_CLASSES) {
68-
if (!ClassUtils.isPresent(className, null)) {
69-
return WebApplicationType.NONE;
70+
return isServletApplication() ? WebApplicationType.SERVLET : WebApplicationType.NONE;
71+
}
72+
73+
private static boolean isServletApplication() {
74+
for (String servletIndicatorClass : SERVLET_INDICATOR_CLASSES) {
75+
if (!ClassUtils.isPresent(servletIndicatorClass, null)) {
76+
return false;
7077
}
7178
}
72-
return WebApplicationType.SERVLET;
79+
return true;
7380
}
7481

7582
static class WebApplicationTypeRuntimeHints implements RuntimeHintsRegistrar {
@@ -79,9 +86,6 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
7986
for (String servletIndicatorClass : SERVLET_INDICATOR_CLASSES) {
8087
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
8188
}
82-
registerTypeIfPresent(JERSEY_INDICATOR_CLASS, classLoader, hints);
83-
registerTypeIfPresent(WEBFLUX_INDICATOR_CLASS, classLoader, hints);
84-
registerTypeIfPresent(WEBMVC_INDICATOR_CLASS, classLoader, hints);
8589
}
8690

8791
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
@@ -92,4 +96,21 @@ private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classL
9296

9397
}
9498

99+
/**
100+
* Strategy that may be implemented by a module that can deduce the
101+
* {@link WebApplicationType}.
102+
*
103+
* @since 4.0.1
104+
*/
105+
@FunctionalInterface
106+
public interface Deducer {
107+
108+
/**
109+
* Deduce the web application type.
110+
* @return the deduced web application type or {@code null}
111+
*/
112+
@Nullable WebApplicationType deduceWebApplicationType();
113+
114+
}
115+
95116
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webflux;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
23+
import org.springframework.aot.hint.TypeReference;
24+
import org.springframework.boot.WebApplicationType;
25+
import org.springframework.core.annotation.Order;
26+
import org.springframework.util.ClassUtils;
27+
28+
/**
29+
* {@link WebApplicationType} deducer to ensure Spring WebFlux applications use
30+
* {@link WebApplicationType#REACTIVE}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
@Order(20) // Ordered after MVC
35+
class WebFluxWebApplicationTypeDeducer implements WebApplicationType.Deducer {
36+
37+
private static final String[] INDICATOR_CLASSES = { "reactor.core.publisher.Mono",
38+
"org.springframework.web.reactive.DispatcherHandler" };
39+
40+
@Override
41+
public @Nullable WebApplicationType deduceWebApplicationType() {
42+
// Guard in case the classic module is being used and dependencies are excluded
43+
for (String indicatorClass : INDICATOR_CLASSES) {
44+
if (!ClassUtils.isPresent(indicatorClass, null)) {
45+
return null;
46+
}
47+
}
48+
return WebApplicationType.REACTIVE;
49+
}
50+
51+
static class WebFluxWebApplicationTypeDeducerRuntimeHints implements RuntimeHintsRegistrar {
52+
53+
@Override
54+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
55+
for (String servletIndicatorClass : INDICATOR_CLASSES) {
56+
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
57+
}
58+
}
59+
60+
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
61+
if (ClassUtils.isPresent(typeName, classLoader)) {
62+
hints.reflection().registerType(TypeReference.of(typeName));
63+
}
64+
}
65+
66+
}
67+
68+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Core integration between Spring Boot and Spring WebFlux.
19+
*/
20+
@NullMarked
21+
package org.springframework.boot.webflux;
22+
23+
import org.jspecify.annotations.NullMarked;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# WebApplicationType Deducers
2+
org.springframework.boot.WebApplicationType$Deducer=\
3+
org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.aot.hint.RuntimeHintsRegistrar=\
2+
org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer$WebFluxWebApplicationTypeDeducerRuntimeHints
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webmvc;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
23+
import org.springframework.aot.hint.TypeReference;
24+
import org.springframework.boot.WebApplicationType;
25+
import org.springframework.core.annotation.Order;
26+
import org.springframework.util.ClassUtils;
27+
28+
/**
29+
* {@link WebApplicationType} deducer to ensure Spring MVC applications use
30+
* {@link WebApplicationType#SERVLET}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
@Order(10) // Ordered after WebFlux
35+
class WebMvcWebApplicationTypeDeducer implements WebApplicationType.Deducer {
36+
37+
private static final String[] INDICATOR_CLASSES = { "jakarta.servlet.Servlet",
38+
"org.springframework.web.servlet.DispatcherServlet",
39+
"org.springframework.web.context.ConfigurableWebApplicationContext" };
40+
41+
@Override
42+
public @Nullable WebApplicationType deduceWebApplicationType() {
43+
// Guard in case the classic module is being used and dependencies are excluded
44+
for (String indicatorClass : INDICATOR_CLASSES) {
45+
if (!ClassUtils.isPresent(indicatorClass, null)) {
46+
return null;
47+
}
48+
}
49+
return WebApplicationType.SERVLET;
50+
}
51+
52+
static class WebMvcWebApplicationTypeDeducerRuntimeHints implements RuntimeHintsRegistrar {
53+
54+
@Override
55+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
56+
for (String servletIndicatorClass : INDICATOR_CLASSES) {
57+
registerTypeIfPresent(servletIndicatorClass, classLoader, hints);
58+
}
59+
}
60+
61+
private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) {
62+
if (ClassUtils.isPresent(typeName, classLoader)) {
63+
hints.reflection().registerType(TypeReference.of(typeName));
64+
}
65+
}
66+
67+
}
68+
69+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Core integration between Spring Boot and Spring Web MVC.
19+
*/
20+
@NullMarked
21+
package org.springframework.boot.webmvc;
22+
23+
import org.jspecify.annotations.NullMarked;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# Template Availability Providers
22
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
33
org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider
4+
5+
# WebApplicationType Deducers
6+
org.springframework.boot.WebApplicationType$Deducer=\
7+
org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer

0 commit comments

Comments
 (0)