Refine global optional config data opt-out

Rename the opt-out property and use an enum in case we want to provide
additional options in the future.

Closes gh-23097
pull/23141/head
Phillip Webb 4 years ago
parent bebb4363d6
commit 0ddd1b6ce8

@ -623,8 +623,8 @@ You can use this prefix with the `spring.config.location` and `spring.config.add
For example, a `spring.config.import` value of `optional:file:./myconfig.properties` allows your application to start, even if the `myconfig.properties` file is missing.
If you want all locations to be considered optional, you can use the `spring.config.all-locations-optional` property.
Set the value to `true` using `SpringApplication.setDefaultProperties(...)` or with a system/environment variable.
If you want to ignore all `ConfigDataLocationNotFoundExceptions` and always continue to start your application, you can use the `spring.config.on-location-not-found` property.
Set the value to `ignore` using `SpringApplication.setDefaultProperties(...)` or with a system/environment variable.

@ -74,10 +74,11 @@ class ConfigDataEnvironment {
static final String IMPORT_PROPERTY = "spring.config.import";
/**
* Property used to determine if all locations are optional and
* {@code ConfigDataLocationNotFoundExceptions} should be ignored.
* Property used to determine what action to take when a
* {@code ConfigDataLocationNotFoundException} is thrown.
* @see ConfigDataLocationNotFoundAction
*/
static final String ALL_LOCATIONS_OPTIONAL_PROPERTY = "spring.config.all-locations-optional";
static final String ON_LOCATION_NOT_FOUND_PROPERTY = "spring.config.on-location-not-found";
/**
* Default search locations used if not {@link #LOCATION_PROPERTY} is found.
@ -120,20 +121,22 @@ class ConfigDataEnvironment {
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
boolean allLocationsOptional = binder.bind(ALL_LOCATIONS_OPTIONAL_PROPERTY, Boolean.class).orElse(false);
ConfigDataLocationNotFoundAction locationNotFoundAction = binder
.bind(ON_LOCATION_NOT_FOUND_PROPERTY, ConfigDataLocationNotFoundAction.class)
.orElse(ConfigDataLocationNotFoundAction.FAIL);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry;
this.environment = environment;
this.resolvers = createConfigDataLocationResolvers(logFactory, allLocationsOptional, binder, resourceLoader);
this.resolvers = createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.loaders = new ConfigDataLoaders(logFactory, allLocationsOptional);
this.loaders = new ConfigDataLoaders(logFactory, locationNotFoundAction);
this.contributors = createContributors(binder);
}
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
boolean allLocationsOptional, Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, allLocationsOptional, binder, resourceLoader);
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
}
private ConfigDataEnvironmentContributors createContributors(Binder binder) {

@ -51,10 +51,11 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
/**
* Property used to determine if all locations are optional and
* {@code ConfigDataLocationNotFoundExceptions} should be ignored.
* Property used to determine what action to take when a
* {@code ConfigDataLocationNotFoundException} is thrown.
* @see ConfigDataLocationNotFoundAction
*/
public static final String ALL_LOCATIONS_OPTIONAL_PROPERTY = ConfigDataEnvironment.ALL_LOCATIONS_OPTIONAL_PROPERTY;
public static final String ON_LOCATION_NOT_FOUND_PROPERTY = ConfigDataEnvironment.ON_LOCATION_NOT_FOUND_PROPERTY;
private final DeferredLogFactory logFactory;

@ -40,7 +40,7 @@ class ConfigDataLoaders {
private final Log logger;
private final boolean allLocationsOptional;
private final ConfigDataLocationNotFoundAction locationNotFoundAction;
private final List<ConfigDataLoader<?>> loaders;
@ -48,22 +48,25 @@ class ConfigDataLoaders {
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param allLocationsOptional if all locations are considered optional
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param logFactory the deferred log factory
*/
ConfigDataLoaders(DeferredLogFactory logFactory, boolean allLocationsOptional) {
this(logFactory, allLocationsOptional, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction) {
this(logFactory, locationNotFoundAction, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param allLocationsOptional if all locations are considered optional
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param names the {@link ConfigDataLoader} class names instantiate
*/
ConfigDataLoaders(DeferredLogFactory logFactory, boolean allLocationsOptional, List<String> names) {
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction,
List<String> names) {
this.logger = logFactory.getLog(getClass());
this.allLocationsOptional = allLocationsOptional;
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> availableParameters.add(Log.class, logFactory::getLog));
this.loaders = instantiator.instantiate(names);
@ -104,11 +107,12 @@ class ConfigDataLoaders {
return loader.load(context, location);
}
catch (ConfigDataLocationNotFoundException ex) {
if (this.allLocationsOptional || optional) {
if (optional) {
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
return null;
}
throw ex;
this.locationNotFoundAction.handle(this.logger, location, ex);
return null;
}
}

@ -0,0 +1,64 @@
/*
* 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 org.apache.commons.logging.Log;
import org.springframework.core.log.LogMessage;
/**
* Action to take when an uncaught {@link ConfigDataLocationNotFoundException} is thrown.
*
* @author Phillip Webb
* @since 2.4.0
*/
public enum ConfigDataLocationNotFoundAction {
/**
* Throw the exception to fail startup.
*/
FAIL {
@Override
void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) {
throw ex;
}
},
/**
* Ignore the exception and continue processing the remaining locations.
*/
IGNORE {
@Override
void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) {
logger.trace(LogMessage.format("Ignoring missing resource from location %s", location));
}
};
/**
* Handle the given exception.
* @param logger the logger used for output
* @param location the location being checked (a {@link ConfigDataLocation} or
* {@code String})
* @param ex the exception to handle
*/
abstract void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex);
}

@ -43,35 +43,37 @@ class ConfigDataLocationResolvers {
private final Log logger;
private final boolean allLocationsOptional;
private final ConfigDataLocationNotFoundAction locationNotFoundAction;
private final List<ConfigDataLocationResolver<?>> resolvers;
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param allLocationsOptional if all locations are considered optional
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param binder a binder providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
*/
ConfigDataLocationResolvers(DeferredLogFactory logFactory, boolean allLocationsOptional, Binder binder,
ResourceLoader resourceLoader) {
this(logFactory, allLocationsOptional, binder, resourceLoader,
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader) {
this(logFactory, locationNotFoundAction, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null));
}
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param allLocationsOptional if all locations are considered optional
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param binder {@link Binder} providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
* @param names the {@link ConfigDataLocationResolver} class names
*/
ConfigDataLocationResolvers(DeferredLogFactory logFactory, boolean allLocationsOptional, Binder binder,
ResourceLoader resourceLoader, List<String> names) {
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader, List<String> names) {
this.logger = logFactory.getLog(getClass());
this.allLocationsOptional = allLocationsOptional;
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
@ -152,11 +154,12 @@ class ConfigDataLocationResolvers {
return resolved;
}
catch (ConfigDataLocationNotFoundException ex) {
if (this.allLocationsOptional || optional) {
if (optional) {
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
return Collections.emptyList();
}
throw ex;
this.locationNotFoundAction.handle(this.logger, location, ex);
return Collections.emptyList();
}
}

@ -80,9 +80,9 @@ class ConfigDataEnvironmentContributorsTests {
void setup() {
this.environment = new MockEnvironment();
this.binder = Binder.get(this.environment);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, true, this.binder,
null);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, null);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL);
this.importer = new ConfigDataImporter(resolvers, loaders);
this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null);
}

@ -558,8 +558,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
}
@Test
void runWhenHasNonOptionalImportAndAllOptionalPropertyIgnoresException() {
this.application.run("--spring.config.all-locations-optional=true",
void runWhenHasNonOptionalImportAndIgnoreNotFoundPropertyDoesNotThrowException() {
this.application.run("--spring.config.on-location-not-found=ignore",
"--spring.config.location=classpath:missing-appplication.properties");
}

@ -214,10 +214,9 @@ class ConfigDataEnvironmentTests {
@Override
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
boolean failOnConfigDataLocationNotFound, Binder binder, ResourceLoader resourceLoader) {
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
this.configDataLocationResolversBinder = binder;
return super.createConfigDataLocationResolvers(logFactory, failOnConfigDataLocationNotFound, binder,
resourceLoader);
return super.createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
}
Binder getConfigDataLocationResolversBinder() {

@ -46,13 +46,14 @@ class ConfigDataLoadersTests {
@Test
void createWhenLoaderHasLogParameterInjectsLog() {
new ConfigDataLoaders(this.logFactory, true, Arrays.asList(LoggingConfigDataLoader.class.getName()));
new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName()));
}
@Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
@ -61,7 +62,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for location test");
@ -70,7 +71,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for location 'test'");
@ -79,7 +80,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);

@ -63,8 +63,9 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName()));
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class);
assertThat(((TestBoundResolver) resolvers.getResolvers().get(0)).getBinder()).isSameAs(this.binder);
@ -72,8 +73,9 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenNotInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, Collections.singletonList(TestResolver.class.getName()));
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class);
}
@ -81,8 +83,9 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, Collections.singletonList(InputStream.class.getName())))
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
}
@ -92,8 +95,8 @@ class ConfigDataLocationResolversTests {
names.add(TestResolver.class.getName());
names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.class.getName());
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, names);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
assertThat(resolvers.getResolvers().get(2)).isExactlyInstanceOf(LowestTestResolver.class);
@ -101,8 +104,8 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllResolvesUsingFirstSupportedResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
Collections.singletonList("LowestTestResolver:test"), null);
@ -115,8 +118,8 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllWhenProfileMergesResolvedLocations() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
Collections.singletonList("LowestTestResolver:test"), this.profiles);
@ -133,8 +136,8 @@ class ConfigDataLocationResolversTests {
@Test
void resolveWhenNoResolverThrowsException() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
.isThrownBy(() -> resolvers.resolveAll(this.context, Collections.singletonList("Missing:test"), null))

Loading…
Cancel
Save