Skip to content

Commit 463a614

Browse files
committed
Fix package tangle between SpringApplication and Environment types
Update `SpringApplication` so that it no longer directly decides the type of `Environment` that should be created. Closes gh-32825
1 parent 81beb2e commit 463a614

11 files changed

+162
-50
lines changed

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

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
import org.springframework.beans.BeanUtils;
2222
import org.springframework.context.ConfigurableApplicationContext;
23-
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
24-
import org.springframework.core.io.support.SpringFactoriesLoader;
23+
import org.springframework.core.env.ConfigurableEnvironment;
24+
import org.springframework.core.env.Environment;
2525

2626
/**
2727
* Strategy interface for creating the {@link ConfigurableApplicationContext} used by a
@@ -40,22 +40,32 @@ public interface ApplicationContextFactory {
4040
* A default {@link ApplicationContextFactory} implementation that will create an
4141
* appropriate context for the {@link WebApplicationType}.
4242
*/
43-
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
44-
try {
45-
for (ApplicationContextFactory candidate : SpringFactoriesLoader
46-
.loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())) {
47-
ConfigurableApplicationContext context = candidate.create(webApplicationType);
48-
if (context != null) {
49-
return context;
50-
}
51-
}
52-
return new AnnotationConfigApplicationContext();
53-
}
54-
catch (Exception ex) {
55-
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
56-
+ "you may need a custom ApplicationContextFactory", ex);
57-
}
58-
};
43+
ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();
44+
45+
/**
46+
* Return the {@link Environment} type expected to be set on the
47+
* {@link #create(WebApplicationType) created} application context. The result of this
48+
* method can be used to convert an existing environment instance to the correct type.
49+
* @param webApplicationType the web application type
50+
* @return the expected application context type or {@code null} to use the default
51+
* @since 2.6.14
52+
*/
53+
default Class<? extends ConfigurableEnvironment> getEnvironmentType(WebApplicationType webApplicationType) {
54+
return null;
55+
}
56+
57+
/**
58+
* Create a new {@link Environment} to be set on the
59+
* {@link #create(WebApplicationType) created} application context. The result of this
60+
* method must match the type returned by
61+
* {@link #getEnvironmentType(WebApplicationType)}.
62+
* @param webApplicationType the web application type
63+
* @return an environment instance or {@code null} to use the default
64+
* @since 2.6.14
65+
*/
66+
default ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
67+
return null;
68+
}
5969

6070
/**
6171
* Creates the {@link ConfigurableApplicationContext application context} for a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2022 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;
18+
19+
import java.util.function.BiFunction;
20+
import java.util.function.Supplier;
21+
22+
import org.springframework.context.ConfigurableApplicationContext;
23+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
24+
import org.springframework.core.env.ConfigurableEnvironment;
25+
import org.springframework.core.io.support.SpringFactoriesLoader;
26+
27+
/**
28+
* Default {@link ApplicationContextFactory} implementation that will create an
29+
* appropriate context for the {@link WebApplicationType}.
30+
*
31+
* @author Phillip Webb
32+
*/
33+
class DefaultApplicationContextFactory implements ApplicationContextFactory {
34+
35+
@Override
36+
public Class<? extends ConfigurableEnvironment> getEnvironmentType(WebApplicationType webApplicationType) {
37+
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::getEnvironmentType, null);
38+
}
39+
40+
@Override
41+
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
42+
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null);
43+
}
44+
45+
@Override
46+
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
47+
try {
48+
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
49+
AnnotationConfigApplicationContext::new);
50+
}
51+
catch (Exception ex) {
52+
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
53+
+ "you may need a custom ApplicationContextFactory", ex);
54+
}
55+
}
56+
57+
private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
58+
BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
59+
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
60+
getClass().getClassLoader())) {
61+
T result = action.apply(candidate, webApplicationType);
62+
if (result != null) {
63+
return result;
64+
}
65+
}
66+
return (defaultResult != null) ? defaultResult.get() : null;
67+
}
68+
69+
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot;
1818

19+
import java.lang.reflect.Constructor;
1920
import java.util.Collections;
2021
import java.util.HashSet;
2122
import java.util.Set;
@@ -26,6 +27,7 @@
2627
import org.springframework.core.env.PropertySource;
2728
import org.springframework.core.env.StandardEnvironment;
2829
import org.springframework.util.ClassUtils;
30+
import org.springframework.util.ReflectionUtils;
2931
import org.springframework.web.context.support.StandardServletEnvironment;
3032

3133
/**
@@ -87,10 +89,12 @@ private StandardEnvironment convertEnvironment(ConfigurableEnvironment environme
8789

8890
private StandardEnvironment createEnvironment(Class<? extends StandardEnvironment> type) {
8991
try {
90-
return type.getDeclaredConstructor().newInstance();
92+
Constructor<? extends StandardEnvironment> constructor = type.getDeclaredConstructor();
93+
ReflectionUtils.makeAccessible(constructor);
94+
return constructor.newInstance();
9195
}
9296
catch (Exception ex) {
93-
return new StandardEnvironment();
97+
return new ApplicationEnvironment();
9498
}
9599
}
96100

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -366,15 +366,18 @@ public StandardEnvironment convertEnvironment(ConfigurableEnvironment environmen
366366
deduceEnvironmentClass());
367367
}
368368

369+
@SuppressWarnings("unchecked")
369370
private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
370-
switch (this.webApplicationType) {
371-
case SERVLET:
372-
return ApplicationServletEnvironment.class;
373-
case REACTIVE:
374-
return ApplicationReactiveWebEnvironment.class;
375-
default:
376-
return ApplicationEnvironment.class;
371+
Class<? extends ConfigurableEnvironment> environmentType = this.applicationContextFactory
372+
.getEnvironmentType(this.webApplicationType);
373+
if (environmentType == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
374+
environmentType = ApplicationContextFactory.DEFAULT.getEnvironmentType(this.webApplicationType);
375+
}
376+
if (environmentType == null) {
377+
return ApplicationEnvironment.class;
377378
}
379+
Assert.isAssignable(StandardEnvironment.class, environmentType);
380+
return (Class<? extends StandardEnvironment>) environmentType;
378381
}
379382

380383
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
@@ -468,14 +471,11 @@ private ConfigurableEnvironment getOrCreateEnvironment() {
468471
if (this.environment != null) {
469472
return this.environment;
470473
}
471-
switch (this.webApplicationType) {
472-
case SERVLET:
473-
return new ApplicationServletEnvironment();
474-
case REACTIVE:
475-
return new ApplicationReactiveWebEnvironment();
476-
default:
477-
return new ApplicationEnvironment();
474+
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
475+
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
476+
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
478477
}
478+
return (environment != null) ? environment : new ApplicationEnvironment();
479479
}
480480

481481
/**

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactor
218218
*/
219219
static class Factory implements ApplicationContextFactory {
220220

221+
@Override
222+
public Class<? extends ConfigurableEnvironment> getEnvironmentType(WebApplicationType webApplicationType) {
223+
return (webApplicationType != WebApplicationType.REACTIVE) ? null : ApplicationReactiveWebEnvironment.class;
224+
}
225+
226+
@Override
227+
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
228+
return (webApplicationType != WebApplicationType.REACTIVE) ? null : new ApplicationReactiveWebEnvironment();
229+
}
230+
221231
@Override
222232
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
223233
return (webApplicationType != WebApplicationType.REACTIVE) ? null

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationReactiveWebEnvironment.java renamed to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironment.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot;
17+
package org.springframework.boot.web.reactive.context;
1818

19+
import org.springframework.boot.SpringApplication;
1920
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
20-
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
2121
import org.springframework.core.env.ConfigurablePropertyResolver;
2222
import org.springframework.core.env.MutablePropertySources;
2323

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,16 @@ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactor
215215
*/
216216
static class Factory implements ApplicationContextFactory {
217217

218+
@Override
219+
public Class<? extends ConfigurableEnvironment> getEnvironmentType(WebApplicationType webApplicationType) {
220+
return (webApplicationType != WebApplicationType.SERVLET) ? null : ApplicationServletEnvironment.class;
221+
}
222+
223+
@Override
224+
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
225+
return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment();
226+
}
227+
218228
@Override
219229
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
220230
return (webApplicationType != WebApplicationType.SERVLET) ? null

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationServletEnvironment.java renamed to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironment.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot;
17+
package org.springframework.boot.web.servlet.context;
1818

19+
import org.springframework.boot.SpringApplication;
1920
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
2021
import org.springframework.core.env.ConfigurablePropertyResolver;
2122
import org.springframework.core.env.MutablePropertySources;

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
7575
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
7676
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
77+
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment;
7778
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
7879
import org.springframework.context.ApplicationContext;
7980
import org.springframework.context.ApplicationContextAware;
@@ -472,15 +473,17 @@ void environmentForWeb() {
472473
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
473474
application.setWebApplicationType(WebApplicationType.SERVLET);
474475
this.context = application.run();
475-
assertThat(this.context.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class);
476+
assertThat(this.context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class);
477+
assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
476478
}
477479

478480
@Test
479481
void environmentForReactiveWeb() {
480482
SpringApplication application = new SpringApplication(ExampleReactiveWebConfig.class);
481483
application.setWebApplicationType(WebApplicationType.REACTIVE);
482484
this.context = application.run();
483-
assertThat(this.context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class);
485+
assertThat(this.context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
486+
assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
484487
}
485488

486489
@Test
@@ -1055,7 +1058,7 @@ void getApplicationArgumentsBean() {
10551058
void webApplicationSwitchedOffInListener() {
10561059
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
10571060
application.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) (event) -> {
1058-
assertThat(event.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class);
1061+
assertThat(event.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
10591062
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(event.getEnvironment(), "foo=bar");
10601063
event.getSpringApplication().setWebApplicationType(WebApplicationType.NONE);
10611064
});
@@ -1081,23 +1084,26 @@ void webApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironmen
10811084
ConfigurableApplicationContext context = new SpringApplication(ExampleWebConfig.class)
10821085
.run("--spring.main.web-application-type=servlet");
10831086
assertThat(context).isInstanceOf(WebApplicationContext.class);
1084-
assertThat(context.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class);
1087+
assertThat(context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class);
1088+
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment");
10851089
}
10861090

10871091
@Test
10881092
void reactiveApplicationConfiguredViaAPropertyHasTheCorrectTypeOfContextAndEnvironment() {
10891093
ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class)
10901094
.run("--spring.main.web-application-type=reactive");
10911095
assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class);
1092-
assertThat(context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class);
1096+
assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
1097+
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
10931098
}
10941099

10951100
@Test
10961101
void environmentIsConvertedIfTypeDoesNotMatch() {
10971102
ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class)
10981103
.run("--spring.profiles.active=withwebapplicationtype");
10991104
assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class);
1100-
assertThat(context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class);
1105+
assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class);
1106+
assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment");
11011107
}
11021108

11031109
@Test

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationReactiveWebEnvironmentTests.java renamed to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironmentTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot;
17+
package org.springframework.boot.web.reactive.context;
1818

19+
import org.springframework.boot.AbstractApplicationEnvironmentTests;
1920
import org.springframework.core.env.StandardEnvironment;
2021

2122
/**

0 commit comments

Comments
 (0)