diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java index 2e2b17349f..354e1de2c4 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -25,13 +25,14 @@ import org.springframework.boot.ResourceBanner; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.context.config.AnsiOutputApplicationListener; -import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.logging.ClasspathLoggingApplicationListener; import org.springframework.boot.context.logging.LoggingApplicationListener; import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration; import org.springframework.boot.devtools.restart.RestartInitializer; import org.springframework.boot.devtools.restart.RestartScopeInitializer; import org.springframework.boot.devtools.restart.Restarter; +import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.core.io.ClassPathResource; @@ -71,7 +72,7 @@ public final class RemoteSpringApplication { private Collection> getListeners() { List> listeners = new ArrayList<>(); listeners.add(new AnsiOutputApplicationListener()); - listeners.add(new ConfigFileApplicationListener()); + listeners.add(new EnvironmentPostProcessorApplicationListener(ConfigDataEnvironmentPostProcessor.class)); listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new LoggingApplicationListener()); listeners.add(new RemoteUrlPropertyExtractor()); diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java new file mode 100644 index 0000000000..40261ffe96 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2020 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.test.context; + +import java.util.function.Supplier; + +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.boot.env.DefaultPropertiesPropertySource; +import org.springframework.boot.env.RandomValuePropertySource; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.test.context.ContextConfiguration; + +/** + * {@link ApplicationContextInitializer} that can be used with the + * {@link ContextConfiguration#initializers()} to trigger loading of {@link ConfigData} + * such as {@literal application.properties}. + * + * @author Phillip Webb + * @since 2.4.0 + * @see ConfigDataEnvironmentPostProcessor + */ +public class ConfigDataApplicationContextInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + RandomValuePropertySource.addToEnvironment(environment); + new ConfigDataProcessor().addPropertySources(environment, applicationContext); + DefaultPropertiesPropertySource.moveToEnd(environment); + } + + private static class ConfigDataProcessor extends ConfigDataEnvironmentPostProcessor { + + ConfigDataProcessor() { + super(Supplier::get); + } + + void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + addPropertySources(environment, resourceLoader, null); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializer.java index b947070933..8c0bfbd843 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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,7 +16,6 @@ package org.springframework.boot.test.context; -import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextConfiguration; @@ -28,14 +27,16 @@ import org.springframework.test.context.ContextConfiguration; * * @author Phillip Webb * @since 1.4.0 - * @see ConfigFileApplicationListener + * @see org.springframework.boot.context.config.ConfigFileApplicationListener + * @deprecated since 2.4.0 in favor of {@link ConfigDataApplicationContextInitializer} */ +@Deprecated public class ConfigFileApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { - new ConfigFileApplicationListener() { + new org.springframework.boot.context.config.ConfigFileApplicationListener() { public void apply() { addPropertySources(applicationContext.getEnvironment(), applicationContext); addPostProcessors(applicationContext); diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerTests.java new file mode 100644 index 0000000000..6c0e03ce6f --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2020 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.test.context; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigDataApplicationContextInitializer}. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@DirtiesContext +@ContextConfiguration(classes = ConfigDataApplicationContextInitializerTests.Config.class, + initializers = ConfigDataApplicationContextInitializer.class) +class ConfigDataApplicationContextInitializerTests { + + @Autowired + private Environment environment; + + @Test + void initializerPopulatesEnvironment() { + assertThat(this.environment.getProperty("foo")).isEqualTo("bucket"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerWithLegacySwitchTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerWithLegacySwitchTests.java new file mode 100644 index 0000000000..9247c3449a --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializerWithLegacySwitchTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2020 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.test.context; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigDataApplicationContextInitializer}. + * + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@DirtiesContext +@TestPropertySource(properties = "spring.config.use-legacy-processing=true") +@ContextConfiguration(classes = ConfigDataApplicationContextInitializerWithLegacySwitchTests.Config.class, + initializers = ConfigDataApplicationContextInitializer.class) +class ConfigDataApplicationContextInitializerWithLegacySwitchTests { + + @Autowired + private Environment environment; + + @Test + void initializerPopulatesEnvironment() { + assertThat(this.environment.getProperty("foo")).isEqualTo("bucket"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializerTests.java index 0455494807..9442839093 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/ConfigFileApplicationContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -33,6 +33,8 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb */ +@Deprecated +@SuppressWarnings("deprecation") @ExtendWith(SpringExtension.class) @DirtiesContext @ContextConfiguration(classes = ConfigFileApplicationContextInitializerTests.Config.class, 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 63046533e6..a5210f0961 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 @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -237,7 +236,7 @@ public class SpringApplication { private Map defaultProperties; - private Set additionalProfiles = new HashSet<>(); + private Set additionalProfiles = Collections.emptySet(); private boolean allowBeanDefinitionOverriding; @@ -352,6 +351,7 @@ public class SpringApplication { ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(environment); DefaultPropertiesPropertySource.moveToEnd(environment); + configureAdditionalProfiles(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, @@ -527,9 +527,16 @@ public class SpringApplication { * @see org.springframework.boot.context.config.ConfigFileApplicationListener */ protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { - Set profiles = new LinkedHashSet<>(this.additionalProfiles); - profiles.addAll(Arrays.asList(environment.getActiveProfiles())); - environment.setActiveProfiles(StringUtils.toStringArray(profiles)); + } + + private void configureAdditionalProfiles(ConfigurableEnvironment environment) { + if (!CollectionUtils.isEmpty(this.additionalProfiles)) { + Set profiles = new LinkedHashSet<>(Arrays.asList(environment.getActiveProfiles())); + if (!profiles.containsAll(this.additionalProfiles)) { + profiles.addAll(this.additionalProfiles); + environment.setActiveProfiles(StringUtils.toStringArray(profiles)); + } + } } private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { @@ -1043,7 +1050,15 @@ public class SpringApplication { * @param profiles the additional profiles to set */ public void setAdditionalProfiles(String... profiles) { - this.additionalProfiles = new LinkedHashSet<>(Arrays.asList(profiles)); + this.additionalProfiles = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(profiles))); + } + + /** + * Return an immutable set of any additional profiles in use. + * @return the additional profiles + */ + public Set getAdditionalProfiles() { + return this.additionalProfiles; } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java index ee60ef6fb7..a0460d4416 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java @@ -22,8 +22,10 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.apache.commons.logging.Log; + import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.json.JsonParser; @@ -92,14 +94,36 @@ import org.springframework.util.StringUtils; public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered, ApplicationListener { - private static final DeferredLog logger = new DeferredLog(); - private static final String VCAP_APPLICATION = "VCAP_APPLICATION"; private static final String VCAP_SERVICES = "VCAP_SERVICES"; + private final Log logger; + + private final boolean switchableLogger; + // Before ConfigFileApplicationListener so values there can use these ones - private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1; + private int order = ConfigDataEnvironmentPostProcessor.ORDER - 1; + + /** + * Create a new {@link CloudFoundryVcapEnvironmentPostProcessor} instance. + * @deprecated since 2.4.0 in favor of + * {@link #CloudFoundryVcapEnvironmentPostProcessor(Log)} + */ + @Deprecated + public CloudFoundryVcapEnvironmentPostProcessor() { + this.logger = new DeferredLog(); + this.switchableLogger = true; + } + + /** + * Create a new {@link CloudFoundryVcapEnvironmentPostProcessor} instance. + * @param logger the logger to use + */ + public CloudFoundryVcapEnvironmentPostProcessor(Log logger) { + this.logger = logger; + this.switchableLogger = false; + } public void setOrder(int order) { this.order = order; @@ -128,9 +152,17 @@ public class CloudFoundryVcapEnvironmentPostProcessor } } + /** + * Event listener used to switch logging. + * @deprecated since 2.4.0 in favor of only using {@link EnvironmentPostProcessor} + * callbacks + */ + @Deprecated @Override public void onApplicationEvent(ApplicationPreparedEvent event) { - logger.switchTo(CloudFoundryVcapEnvironmentPostProcessor.class); + if (this.switchableLogger) { + ((DeferredLog) this.logger).switchTo(CloudFoundryVcapEnvironmentPostProcessor.class); + } } private void addWithPrefix(Properties properties, Properties other, String prefix) { @@ -148,7 +180,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor extractPropertiesFromApplication(properties, map); } catch (Exception ex) { - logger.error("Could not parse VCAP_APPLICATION", ex); + this.logger.error("Could not parse VCAP_APPLICATION", ex); } return properties; } @@ -161,7 +193,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor extractPropertiesFromServices(properties, map); } catch (Exception ex) { - logger.error("Could not parse VCAP_SERVICES", ex); + this.logger.error("Could not parse VCAP_SERVICES", ex); } return properties; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java index 5796920ed0..931f021bbe 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/AnsiOutputApplicationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -20,6 +20,7 @@ import org.springframework.boot.ansi.AnsiOutput; import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; @@ -46,8 +47,8 @@ public class AnsiOutputApplicationListener @Override public int getOrder() { - // Apply after ConfigFileApplicationListener has called EnvironmentPostProcessors - return ConfigFileApplicationListener.DEFAULT_ORDER + 1; + // Apply after EnvironmentPostProcessorApplicationListener + return EnvironmentPostProcessorApplicationListener.DEFAULT_ORDER + 1; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java new file mode 100644 index 0000000000..28ec4915e7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2020 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.context.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.util.Assert; + +/** + * Configuration data that has been loaded from an external {@link ConfigDataLocation + * location} and may ultimately contribute {@link PropertySource property sources} to + * Spring's {@link Environment}. + * + * @author Phillip Webb + * @author Madhura Bhave + * @since 2.4.0 + * @see ConfigDataLocationResolver + * @see ConfigDataLoader + */ +public final class ConfigData { + + private final List> propertySources; + + private final Set