Skip to content

Commit dfb97eb

Browse files
committed
Convert environment if webApplicationType changes
If the web application type is set via properties, it is available only after binding. The environment needs to be converted to the appropriate type if it does not match. If a custom environment is set, it is not converted. Fixes gh-13977
1 parent 6e5ff77 commit dfb97eb

File tree

5 files changed

+173
-37
lines changed

5 files changed

+173
-37
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EnvironmentConverter.java

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
import org.springframework.core.env.PropertySource;
2727
import org.springframework.core.env.StandardEnvironment;
2828
import org.springframework.util.ClassUtils;
29-
import org.springframework.web.context.ConfigurableWebEnvironment;
3029
import org.springframework.web.context.support.StandardServletEnvironment;
3130

3231
/**
3332
* Utility class for converting one type of {@link Environment} to another.
3433
*
3534
* @author Ethan Rubinson
3635
* @author Andy Wilkinson
36+
* @author Madhura Bhave
3737
*/
3838
final class EnvironmentConverter {
3939

@@ -61,60 +61,76 @@ final class EnvironmentConverter {
6161
}
6262

6363
/**
64-
* Converts the given {@code environment} to a {@link StandardEnvironment}. If the
65-
* environment is already a {@code StandardEnvironment} and is not a
66-
* {@link ConfigurableWebEnvironment} no conversion is performed and it is returned
67-
* unchanged.
64+
* Converts the given {@code environment} to the given {@link StandardEnvironment}
65+
* type. If the environment is already of the same type, no conversion is performed
66+
* and it is returned unchanged.
6867
* @param environment the Environment to convert
68+
* @param conversionType the type to convert the Environment to
6969
* @return the converted Environment
7070
*/
71-
StandardEnvironment convertToStandardEnvironmentIfNecessary(
72-
ConfigurableEnvironment environment) {
73-
if (environment instanceof StandardEnvironment
74-
&& !isWebEnvironment(environment, this.classLoader)) {
71+
StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment environment,
72+
Class<? extends StandardEnvironment> conversionType) {
73+
if (conversionType.equals(environment.getClass())) {
7574
return (StandardEnvironment) environment;
7675
}
77-
return convertToStandardEnvironment(environment);
76+
return convertEnvironment(environment, conversionType);
7877
}
7978

80-
private boolean isWebEnvironment(ConfigurableEnvironment environment,
81-
ClassLoader classLoader) {
82-
try {
83-
Class<?> webEnvironmentClass = ClassUtils
84-
.forName(CONFIGURABLE_WEB_ENVIRONMENT_CLASS, classLoader);
85-
return (webEnvironmentClass.isInstance(environment));
86-
}
87-
catch (Throwable ex) {
88-
return false;
89-
}
90-
}
91-
92-
private StandardEnvironment convertToStandardEnvironment(
93-
ConfigurableEnvironment environment) {
94-
StandardEnvironment result = new StandardEnvironment();
79+
private StandardEnvironment convertEnvironment(ConfigurableEnvironment environment,
80+
Class<? extends StandardEnvironment> conversionType) {
81+
StandardEnvironment result = createEnvironment(conversionType);
9582
result.setActiveProfiles(environment.getActiveProfiles());
9683
result.setConversionService(environment.getConversionService());
97-
copyNonServletPropertySources(environment, result);
84+
copyPropertySources(environment, result);
9885
return result;
9986
}
10087

101-
private void copyNonServletPropertySources(ConfigurableEnvironment source,
88+
private StandardEnvironment createEnvironment(
89+
Class<? extends StandardEnvironment> conversionType) {
90+
try {
91+
return conversionType.newInstance();
92+
}
93+
catch (Exception ex) {
94+
return new StandardEnvironment();
95+
}
96+
}
97+
98+
private void copyPropertySources(ConfigurableEnvironment source,
10299
StandardEnvironment target) {
103-
removeAllPropertySources(target.getPropertySources());
100+
removePropertySources(target.getPropertySources(),
101+
isServletEnvironment(target.getClass(), this.classLoader));
104102
for (PropertySource<?> propertySource : source.getPropertySources()) {
105103
if (!SERVLET_ENVIRONMENT_SOURCE_NAMES.contains(propertySource.getName())) {
106104
target.getPropertySources().addLast(propertySource);
107105
}
108106
}
109107
}
110108

111-
private void removeAllPropertySources(MutablePropertySources propertySources) {
109+
private boolean isServletEnvironment(Class<?> conversionType,
110+
ClassLoader classLoader) {
111+
try {
112+
Class<?> webEnvironmentClass = ClassUtils
113+
.forName(CONFIGURABLE_WEB_ENVIRONMENT_CLASS, classLoader);
114+
return webEnvironmentClass.isAssignableFrom(conversionType);
115+
}
116+
catch (Throwable ex) {
117+
return false;
118+
}
119+
}
120+
121+
private void removePropertySources(MutablePropertySources propertySources,
122+
boolean isServletEnvironment) {
112123
Set<String> names = new HashSet<>();
113124
for (PropertySource<?> propertySource : propertySources) {
114125
names.add(propertySource.getName());
115126
}
116127
for (String name : names) {
117-
propertySources.remove(name);
128+
if (!isServletEnvironment) {
129+
propertySources.remove(name);
130+
}
131+
else if (!SERVLET_ENVIRONMENT_SOURCE_NAMES.contains(name)) {
132+
propertySources.remove(name);
133+
}
118134
}
119135
}
120136

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.boot.context.properties.bind.Bindable;
4444
import org.springframework.boot.context.properties.bind.Binder;
4545
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
46+
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
4647
import org.springframework.context.ApplicationContext;
4748
import org.springframework.context.ApplicationContextInitializer;
4849
import org.springframework.context.ApplicationListener;
@@ -235,6 +236,8 @@ public class SpringApplication {
235236

236237
private Set<String> additionalProfiles = new HashSet<>();
237238

239+
private boolean isCustomEnvironment = false;
240+
238241
/**
239242
* Create a new {@link SpringApplication} instance. The application context will load
240243
* beans from the specified primary sources (see {@link SpringApplication class-level}
@@ -360,14 +363,24 @@ private ConfigurableEnvironment prepareEnvironment(
360363
configureEnvironment(environment, applicationArguments.getSourceArgs());
361364
listeners.environmentPrepared(environment);
362365
bindToSpringApplication(environment);
363-
if (this.webApplicationType == WebApplicationType.NONE) {
366+
if (!this.isCustomEnvironment) {
364367
environment = new EnvironmentConverter(getClassLoader())
365-
.convertToStandardEnvironmentIfNecessary(environment);
368+
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
366369
}
367370
ConfigurationPropertySources.attach(environment);
368371
return environment;
369372
}
370373

374+
private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
375+
if (this.webApplicationType == WebApplicationType.SERVLET) {
376+
return StandardServletEnvironment.class;
377+
}
378+
if (this.webApplicationType == WebApplicationType.REACTIVE) {
379+
return StandardReactiveWebEnvironment.class;
380+
}
381+
return StandardEnvironment.class;
382+
}
383+
371384
private void prepareContext(ConfigurableApplicationContext context,
372385
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
373386
ApplicationArguments applicationArguments, Banner printedBanner) {
@@ -462,6 +475,9 @@ private ConfigurableEnvironment getOrCreateEnvironment() {
462475
if (this.webApplicationType == WebApplicationType.SERVLET) {
463476
return new StandardServletEnvironment();
464477
}
478+
if (this.webApplicationType == WebApplicationType.REACTIVE) {
479+
return new StandardReactiveWebEnvironment();
480+
}
465481
return new StandardEnvironment();
466482
}
467483

@@ -1077,6 +1093,7 @@ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
10771093
* @param environment the environment
10781094
*/
10791095
public void setEnvironment(ConfigurableEnvironment environment) {
1096+
this.isCustomEnvironment = true;
10801097
this.environment = environment;
10811098
}
10821099

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/EnvironmentConverterTests.java

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package org.springframework.boot;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1922
import org.junit.Test;
2023

2124
import org.springframework.core.convert.support.ConfigurableConversionService;
2225
import org.springframework.core.env.AbstractEnvironment;
26+
import org.springframework.core.env.PropertySource;
2327
import org.springframework.core.env.StandardEnvironment;
2428
import org.springframework.mock.env.MockEnvironment;
2529
import org.springframework.web.context.support.StandardServletEnvironment;
@@ -32,6 +36,7 @@
3236
*
3337
* @author Ethan Rubinson
3438
* @author Andy Wilkinson
39+
* @author Madhura Bhave
3540
*/
3641
public class EnvironmentConverterTests {
3742

@@ -43,7 +48,8 @@ public void convertedEnvironmentHasSameActiveProfiles() {
4348
AbstractEnvironment originalEnvironment = new MockEnvironment();
4449
originalEnvironment.setActiveProfiles("activeProfile1", "activeProfile2");
4550
StandardEnvironment convertedEnvironment = this.environmentConverter
46-
.convertToStandardEnvironmentIfNecessary(originalEnvironment);
51+
.convertEnvironmentIfNecessary(originalEnvironment,
52+
StandardEnvironment.class);
4753
assertThat(convertedEnvironment.getActiveProfiles())
4854
.containsExactly("activeProfile1", "activeProfile2");
4955
}
@@ -55,25 +61,72 @@ public void convertedEnvironmentHasSameConversionService() {
5561
ConfigurableConversionService.class);
5662
originalEnvironment.setConversionService(conversionService);
5763
StandardEnvironment convertedEnvironment = this.environmentConverter
58-
.convertToStandardEnvironmentIfNecessary(originalEnvironment);
64+
.convertEnvironmentIfNecessary(originalEnvironment,
65+
StandardEnvironment.class);
5966
assertThat(convertedEnvironment.getConversionService())
6067
.isEqualTo(conversionService);
6168
}
6269

6370
@Test
64-
public void standardEnvironmentIsReturnedUnconverted() {
71+
public void envClassSameShouldReturnEnvironmentUnconverted() {
6572
StandardEnvironment standardEnvironment = new StandardEnvironment();
6673
StandardEnvironment convertedEnvironment = this.environmentConverter
67-
.convertToStandardEnvironmentIfNecessary(standardEnvironment);
74+
.convertEnvironmentIfNecessary(standardEnvironment,
75+
StandardEnvironment.class);
6876
assertThat(convertedEnvironment).isSameAs(standardEnvironment);
6977
}
7078

7179
@Test
7280
public void standardServletEnvironmentIsConverted() {
7381
StandardServletEnvironment standardServletEnvironment = new StandardServletEnvironment();
7482
StandardEnvironment convertedEnvironment = this.environmentConverter
75-
.convertToStandardEnvironmentIfNecessary(standardServletEnvironment);
83+
.convertEnvironmentIfNecessary(standardServletEnvironment,
84+
StandardEnvironment.class);
7685
assertThat(convertedEnvironment).isNotSameAs(standardServletEnvironment);
7786
}
7887

88+
@Test
89+
public void servletPropertySourcesAreNotCopiedOverIfNotWebEnvironment() {
90+
StandardServletEnvironment standardServletEnvironment = new StandardServletEnvironment();
91+
StandardEnvironment convertedEnvironment = this.environmentConverter
92+
.convertEnvironmentIfNecessary(standardServletEnvironment,
93+
StandardEnvironment.class);
94+
assertThat(convertedEnvironment).isNotSameAs(standardServletEnvironment);
95+
Set<String> names = new HashSet<>();
96+
for (PropertySource<?> propertySource : convertedEnvironment
97+
.getPropertySources()) {
98+
names.add(propertySource.getName());
99+
}
100+
assertThat(names).doesNotContain(
101+
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
102+
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
103+
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME);
104+
}
105+
106+
@Test
107+
public void envClassSameShouldReturnEnvironmentUnconvertedEvenForWeb() {
108+
StandardServletEnvironment standardServletEnvironment = new StandardServletEnvironment();
109+
StandardEnvironment convertedEnvironment = this.environmentConverter
110+
.convertEnvironmentIfNecessary(standardServletEnvironment,
111+
StandardServletEnvironment.class);
112+
assertThat(convertedEnvironment).isSameAs(standardServletEnvironment);
113+
}
114+
115+
@Test
116+
public void servletPropertySourcesArePresentWhenTypeToConvertIsWeb() {
117+
StandardEnvironment standardEnvironment = new StandardEnvironment();
118+
StandardEnvironment convertedEnvironment = this.environmentConverter
119+
.convertEnvironmentIfNecessary(standardEnvironment,
120+
StandardServletEnvironment.class);
121+
assertThat(convertedEnvironment).isNotSameAs(standardEnvironment);
122+
Set<String> names = new HashSet<>();
123+
for (PropertySource<?> propertySource : convertedEnvironment
124+
.getPropertySources()) {
125+
names.add(propertySource.getName());
126+
}
127+
assertThat(names).contains(
128+
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
129+
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME);
130+
}
131+
79132
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
6161
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
6262
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
63+
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
6364
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
6465
import org.springframework.context.ApplicationContext;
6566
import org.springframework.context.ApplicationContextAware;
@@ -413,6 +414,25 @@ public void defaultApplicationContextForReactiveWeb() {
413414
.isInstanceOf(AnnotationConfigReactiveWebServerApplicationContext.class);
414415
}
415416

417+
@Test
418+
public void environmentForWeb() {
419+
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
420+
application.setWebApplicationType(WebApplicationType.SERVLET);
421+
this.context = application.run();
422+
assertThat(this.context.getEnvironment())
423+
.isInstanceOf(StandardServletEnvironment.class);
424+
}
425+
426+
@Test
427+
public void environmentForReactiveWeb() {
428+
SpringApplication application = new SpringApplication(
429+
ExampleReactiveWebConfig.class);
430+
application.setWebApplicationType(WebApplicationType.REACTIVE);
431+
this.context = application.run();
432+
assertThat(this.context.getEnvironment())
433+
.isInstanceOf(StandardReactiveWebEnvironment.class);
434+
}
435+
416436
@Test
417437
public void customEnvironment() {
418438
TestSpringApplication application = new TestSpringApplication(
@@ -1098,6 +1118,35 @@ public void nonWebApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAnd
10981118
.isNotInstanceOfAny(ConfigurableWebEnvironment.class);
10991119
}
11001120

1121+
@Test
1122+
public void webApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironment() {
1123+
ConfigurableApplicationContext context = new SpringApplication(
1124+
ExampleWebConfig.class).run("--spring.main.web-application-type=servlet");
1125+
assertThat(context).isInstanceOfAny(WebApplicationContext.class);
1126+
assertThat(context.getEnvironment())
1127+
.isInstanceOfAny(StandardServletEnvironment.class);
1128+
}
1129+
1130+
@Test
1131+
public void reactiveApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironment() {
1132+
ConfigurableApplicationContext context = new SpringApplication(
1133+
ExampleReactiveWebConfig.class)
1134+
.run("--spring.main.web-application-type=reactive");
1135+
assertThat(context).isInstanceOfAny(ReactiveWebApplicationContext.class);
1136+
assertThat(context.getEnvironment())
1137+
.isInstanceOfAny(StandardReactiveWebEnvironment.class);
1138+
}
1139+
1140+
@Test
1141+
public void environmentIsConvertedIfTypeDoesNotMatch() {
1142+
ConfigurableApplicationContext context = new SpringApplication(
1143+
ExampleReactiveWebConfig.class)
1144+
.run("--spring.profiles.active=withwebapplicationtype");
1145+
assertThat(context).isInstanceOfAny(ReactiveWebApplicationContext.class);
1146+
assertThat(context.getEnvironment())
1147+
.isInstanceOfAny(StandardReactiveWebEnvironment.class);
1148+
}
1149+
11011150
@Test
11021151
public void failureResultsInSingleStackTrace() throws Exception {
11031152
ThreadGroup group = new ThreadGroup("main");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spring.main.web-application-type: reactive

0 commit comments

Comments
 (0)