diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java index 9f9d08e71aa..7d0a5172291 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java @@ -20,8 +20,8 @@ import java.util.function.Supplier; import org.springframework.beans.BeanUtils; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; /** * Strategy interface for creating the {@link ConfigurableApplicationContext} used by a @@ -40,22 +40,32 @@ public interface ApplicationContextFactory { * A default {@link ApplicationContextFactory} implementation that will create an * appropriate context for the {@link WebApplicationType}. */ - ApplicationContextFactory DEFAULT = (webApplicationType) -> { - try { - for (ApplicationContextFactory candidate : SpringFactoriesLoader - .loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())) { - ConfigurableApplicationContext context = candidate.create(webApplicationType); - if (context != null) { - return context; - } - } - return new AnnotationConfigApplicationContext(); - } - catch (Exception ex) { - throw new IllegalStateException("Unable create a default ApplicationContext instance, " - + "you may need a custom ApplicationContextFactory", ex); - } - }; + ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory(); + + /** + * Return the {@link Environment} type expected to be set on the + * {@link #create(WebApplicationType) created} application context. The result of this + * method can be used to convert an existing environment instance to the correct type. + * @param webApplicationType the web application type + * @return the expected application context type or {@code null} to use the default + * @since 2.6.14 + */ + default Class getEnvironmentType(WebApplicationType webApplicationType) { + return null; + } + + /** + * Create a new {@link Environment} to be set on the + * {@link #create(WebApplicationType) created} application context. The result of this + * method must match the type returned by + * {@link #getEnvironmentType(WebApplicationType)}. + * @param webApplicationType the web application type + * @return an environment instance or {@code null} to use the default + * @since 2.6.14 + */ + default ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + return null; + } /** * Creates the {@link ConfigurableApplicationContext application context} for a diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java new file mode 100644 index 00000000000..aaf33fbce76 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2022 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; + +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * Default {@link ApplicationContextFactory} implementation that will create an + * appropriate context for the {@link WebApplicationType}. + * + * @author Phillip Webb + */ +class DefaultApplicationContextFactory implements ApplicationContextFactory { + + @Override + public Class getEnvironmentType(WebApplicationType webApplicationType) { + return getFromSpringFactories(webApplicationType, ApplicationContextFactory::getEnvironmentType, null); + } + + @Override + public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null); + } + + @Override + public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { + try { + return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, + AnnotationConfigApplicationContext::new); + } + catch (Exception ex) { + throw new IllegalStateException("Unable create a default ApplicationContext instance, " + + "you may need a custom ApplicationContextFactory", ex); + } + } + + private T getFromSpringFactories(WebApplicationType webApplicationType, + BiFunction action, Supplier defaultResult) { + for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, + getClass().getClassLoader())) { + T result = action.apply(candidate, webApplicationType); + if (result != null) { + return result; + } + } + return (defaultResult != null) ? defaultResult.get() : null; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EnvironmentConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EnvironmentConverter.java index 0a79361a084..d9e7b36cf69 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EnvironmentConverter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EnvironmentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -16,6 +16,7 @@ package org.springframework.boot; +import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -26,6 +27,7 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.web.context.support.StandardServletEnvironment; /** @@ -87,10 +89,12 @@ final class EnvironmentConverter { private StandardEnvironment createEnvironment(Class type) { try { - return type.getDeclaredConstructor().newInstance(); + Constructor constructor = type.getDeclaredConstructor(); + ReflectionUtils.makeAccessible(constructor); + return constructor.newInstance(); } catch (Exception ex) { - return new StandardEnvironment(); + return new ApplicationEnvironment(); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 9c0acfc043a..59835b9409d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -366,15 +366,18 @@ public class SpringApplication { deduceEnvironmentClass()); } + @SuppressWarnings("unchecked") private Class deduceEnvironmentClass() { - switch (this.webApplicationType) { - case SERVLET: - return ApplicationServletEnvironment.class; - case REACTIVE: - return ApplicationReactiveWebEnvironment.class; - default: - return ApplicationEnvironment.class; + Class environmentType = this.applicationContextFactory + .getEnvironmentType(this.webApplicationType); + if (environmentType == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) { + environmentType = ApplicationContextFactory.DEFAULT.getEnvironmentType(this.webApplicationType); } + if (environmentType == null) { + return ApplicationEnvironment.class; + } + Assert.isAssignable(StandardEnvironment.class, environmentType); + return (Class) environmentType; } private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, @@ -468,14 +471,11 @@ public class SpringApplication { if (this.environment != null) { return this.environment; } - switch (this.webApplicationType) { - case SERVLET: - return new ApplicationServletEnvironment(); - case REACTIVE: - return new ApplicationReactiveWebEnvironment(); - default: - return new ApplicationEnvironment(); + ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType); + if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) { + environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType); } + return (environment != null) ? environment : new ApplicationEnvironment(); } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java index 9c9e3b6974f..42e342088aa 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/AnnotationConfigReactiveWebServerApplicationContext.java @@ -218,6 +218,16 @@ public class AnnotationConfigReactiveWebServerApplicationContext extends Reactiv */ static class Factory implements ApplicationContextFactory { + @Override + public Class getEnvironmentType(WebApplicationType webApplicationType) { + return (webApplicationType != WebApplicationType.REACTIVE) ? null : ApplicationReactiveWebEnvironment.class; + } + + @Override + public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + return (webApplicationType != WebApplicationType.REACTIVE) ? null : new ApplicationReactiveWebEnvironment(); + } + @Override public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { return (webApplicationType != WebApplicationType.REACTIVE) ? null diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationReactiveWebEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironment.java similarity index 88% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationReactiveWebEnvironment.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironment.java index 6f0324e65f0..cf44fcad367 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationReactiveWebEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.springframework.boot; +package org.springframework.boot.web.reactive.context; +import org.springframework.boot.SpringApplication; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.MutablePropertySources; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java index 2513b0cf3f4..cc908d8b3a9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java @@ -215,6 +215,16 @@ public class AnnotationConfigServletWebServerApplicationContext extends ServletW */ static class Factory implements ApplicationContextFactory { + @Override + public Class getEnvironmentType(WebApplicationType webApplicationType) { + return (webApplicationType != WebApplicationType.SERVLET) ? null : ApplicationServletEnvironment.class; + } + + @Override + public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment(); + } + @Override public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { return (webApplicationType != WebApplicationType.SERVLET) ? null diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationServletEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironment.java similarity index 89% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationServletEnvironment.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironment.java index 22c3eedd319..3cda2f4e61a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationServletEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.boot; +package org.springframework.boot.web.servlet.context; +import org.springframework.boot.SpringApplication; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.MutablePropertySources; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index db50a117a44..70368111277 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -74,6 +74,7 @@ import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext; +import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -472,7 +473,8 @@ class SpringApplicationTests { SpringApplication application = new SpringApplication(ExampleWebConfig.class); application.setWebApplicationType(WebApplicationType.SERVLET); this.context = application.run(); - assertThat(this.context.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class); + assertThat(this.context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class); + assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment"); } @Test @@ -480,7 +482,8 @@ class SpringApplicationTests { SpringApplication application = new SpringApplication(ExampleReactiveWebConfig.class); application.setWebApplicationType(WebApplicationType.REACTIVE); this.context = application.run(); - assertThat(this.context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class); + assertThat(this.context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class); + assertThat(this.context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment"); } @Test @@ -1055,7 +1058,7 @@ class SpringApplicationTests { void webApplicationSwitchedOffInListener() { TestSpringApplication application = new TestSpringApplication(ExampleConfig.class); application.addListeners((ApplicationListener) (event) -> { - assertThat(event.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class); + assertThat(event.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment"); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(event.getEnvironment(), "foo=bar"); event.getSpringApplication().setWebApplicationType(WebApplicationType.NONE); }); @@ -1081,7 +1084,8 @@ class SpringApplicationTests { ConfigurableApplicationContext context = new SpringApplication(ExampleWebConfig.class) .run("--spring.main.web-application-type=servlet"); assertThat(context).isInstanceOf(WebApplicationContext.class); - assertThat(context.getEnvironment()).isInstanceOf(ApplicationServletEnvironment.class); + assertThat(context.getEnvironment()).isInstanceOf(StandardServletEnvironment.class); + assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationServletEnvironment"); } @Test @@ -1089,7 +1093,8 @@ class SpringApplicationTests { ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class) .run("--spring.main.web-application-type=reactive"); assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class); - assertThat(context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class); + assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class); + assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment"); } @Test @@ -1097,7 +1102,8 @@ class SpringApplicationTests { ConfigurableApplicationContext context = new SpringApplication(ExampleReactiveWebConfig.class) .run("--spring.profiles.active=withwebapplicationtype"); assertThat(context).isInstanceOf(ReactiveWebApplicationContext.class); - assertThat(context.getEnvironment()).isInstanceOf(ApplicationReactiveWebEnvironment.class); + assertThat(context.getEnvironment()).isInstanceOf(StandardReactiveWebEnvironment.class); + assertThat(context.getEnvironment().getClass().getName()).endsWith("ApplicationReactiveWebEnvironment"); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationReactiveWebEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironmentTests.java similarity index 83% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationReactiveWebEnvironmentTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironmentTests.java index 3f9a0cac827..1309ff6a28e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationReactiveWebEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ApplicationReactiveWebEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.boot; +package org.springframework.boot.web.reactive.context; +import org.springframework.boot.AbstractApplicationEnvironmentTests; import org.springframework.core.env.StandardEnvironment; /** diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationServletEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironmentTests.java similarity index 83% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationServletEnvironmentTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironmentTests.java index f2beed2fe61..a773e75cdb5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationServletEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ApplicationServletEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.boot; +package org.springframework.boot.web.servlet.context; +import org.springframework.boot.AbstractApplicationEnvironmentTests; import org.springframework.core.env.StandardEnvironment; /**