Refactor BootstrapRegistry support

Refactor `BootstrapRegistry` support following initial prototype work
with the Spring Cloud team.

This update splits the `BootstrapRegistry` API into `BootstrapRegistry`,
`BootstrapContext` and  `ConfigurableBootstrapContext` interfaces and
moves it to the same package as `SpringApplication`.

A new `Bootstrapper` interface has been introduced that can be added
to the `SpringApplication` to customize the `BootstrapRegistry` before
it's used.

Closes gh-23326
pull/23391/head
Phillip Webb 4 years ago
parent 27095d9043
commit 1ae1436211

@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
@ -78,8 +79,8 @@ class LiquibaseAutoConfigurationTests {
@BeforeEach
void init() {
new LiquibaseServiceLocatorApplicationListener()
.onApplicationEvent(new ApplicationStartingEvent(new SpringApplication(Object.class), new String[0]));
new LiquibaseServiceLocatorApplicationListener().onApplicationEvent(new ApplicationStartingEvent(
new DefaultBootstrapContext(), new SpringApplication(Object.class), new String[0]));
}
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()

@ -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.
@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
@ -90,9 +91,10 @@ class RestartApplicationListenerTests {
private void testInitialize(boolean failed) {
Restarter.clearInstance();
RestartApplicationListener listener = new RestartApplicationListener();
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
SpringApplication application = new SpringApplication();
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
listener.onApplicationEvent(new ApplicationStartingEvent(application, ARGS));
listener.onApplicationEvent(new ApplicationStartingEvent(bootstrapContext, application, ARGS));
assertThat(Restarter.getInstance()).isNotEqualTo(nullValue());
assertThat(Restarter.getInstance().isFinished()).isFalse();
listener.onApplicationEvent(new ApplicationPreparedEvent(application, ARGS, context));

@ -16,10 +16,10 @@
package org.springframework.boot.test.context;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
@ -42,9 +42,9 @@ public class ConfigDataApplicationContextInitializer
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
RandomValuePropertySource.addToEnvironment(environment);
DefaultBootstrapRegisty bootstrapRegistry = new DefaultBootstrapRegisty();
ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapRegistry);
bootstrapRegistry.applicationContextPrepared(applicationContext);
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapContext);
bootstrapContext.close(applicationContext);
DefaultPropertiesPropertySource.moveToEnd(environment);
}

@ -0,0 +1,44 @@
/*
* 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;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
/**
* A simple bootstrap context that is available during startup and {@link Environment}
* post-processing up to the point that the {@link ApplicationContext} is prepared.
* <p>
* Provides lazy access to singletons that may be expensive to create, or need to be
* shared before the {@link ApplicationContext} is available.
*
* @author Phillip Webb
* @since 2.4.0
*/
public interface BootstrapContext {
/**
* Return an instance from the context, creating it if it hasn't been accessed
* previously.
* @param <T> the instance type
* @param type the instance type
* @return the instance managed by the context
* @throws IllegalStateException if the type has not been registered
*/
<T> T get(Class<T> type) throws IllegalStateException;
}

@ -0,0 +1,54 @@
/*
* 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;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
/**
* {@link ApplicationEvent} published by a {@link BootstrapContext} when it's closed.
*
* @author Phillip Webb
* @since 2.4.0
* @see BootstrapRegistry#addCloseListener(org.springframework.context.ApplicationListener)
*/
public class BootstrapContextClosedEvent extends ApplicationEvent {
private final ConfigurableApplicationContext applicationContext;
BootstrapContextClosedEvent(BootstrapContext source, ConfigurableApplicationContext applicationContext) {
super(source);
this.applicationContext = applicationContext;
}
/**
* Return the {@link BootstrapContext} that was closed.
* @return the bootstrap context
*/
public BootstrapContext getBootstrapContext() {
return (BootstrapContext) this.source;
}
/**
* Return the prepared application context.
* @return the application context
*/
public ConfigurableApplicationContext getApplicationContext() {
return this.applicationContext;
}
}

@ -0,0 +1,130 @@
/*
* 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;
import java.util.function.Supplier;
import io.undertow.servlet.api.InstanceFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
/**
* A simple object registry that is available during startup and {@link Environment}
* post-processing up to the point that the {@link ApplicationContext} is prepared.
* <p>
* Can be used to register instances that may be expensive to create, or need to be shared
* before the {@link ApplicationContext} is available.
* <p>
* The registry uses {@link Class} as a key, meaning that only a single instance of a
* given type can be stored.
* <p>
* The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener
* that can perform actions when {@link BootstrapContext} has been closed and the
* {@link ApplicationContext} is fully prepared. For example, an instance may choose to
* register itself as a regular Spring bean so that it is available for the application to
* use.
*
* @author Phillip Webb
* @since 2.4.0
* @see BootstrapContext
* @see ConfigurableBootstrapContext
*/
public interface BootstrapRegistry {
/**
* Register a specific type with the registry. If the specified type has already been
* registered, but not get obtained, it will be replaced.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier the instance supplier
*/
<T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);
/**
* Register a specific type with the registry if one is not already present.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier the instance supplier
*/
<T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);
/**
* Return if a registration exists for the given type.
* @param <T> the instance type
* @param type the instance type
* @return {@code true} if the type has already been registered
*/
<T> boolean isRegistered(Class<T> type);
/**
* Return any existing {@link InstanceFactory} for the given type.
* @param <T> the instance type
* @param type the instance type
* @return the registered {@link InstanceSupplier} or {@code null}
*/
<T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);
/**
* Add an {@link ApplicationListener} that will be called with a
* {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and
* the {@link ApplicationContext} has been prepared.
* @param listener the listener to add
*/
void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);
/**
* Supplier used to provide the actual instance the first time it is accessed.
*
* @param <T> the instance type
*/
public interface InstanceSupplier<T> {
/**
* Factory method used to create the instance when needed.
* @param context the {@link BootstrapContext} which may be used to obtain other
* bootstrap instances.
* @return the instance
*/
T get(BootstrapContext context);
/**
* Factory method that can be used to create a {@link InstanceFactory} for a given
* instance.
* @param <T> the instance type
* @param instance the instance
* @return a new {@link InstanceFactory}
*/
static <T> InstanceSupplier<T> of(T instance) {
return (registry) -> instance;
}
/**
* Factory method that can be used to create a {@link InstanceFactory} from a
* {@link Supplier}.
* @param <T> the instance type
* @param supplier the supplier that will provide the instance
* @return a new {@link InstanceFactory}
*/
static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
return (registry) -> (supplier != null) ? supplier.get() : null;
}
}
}

@ -0,0 +1,36 @@
/*
* 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;
/**
* Callback interface that can be used to initialize a {@link BootstrapRegistry} before it
* is used.
*
* @author Phillip Webb
* @since 2.4.0
* @see SpringApplication#addBootstrapper(Bootstrapper)
* @see BootstrapRegistry
*/
public interface Bootstrapper {
/**
* Initialize the given {@link BootstrapRegistry} with any required registrations.
* @param registry the registry to initialize
*/
void intitialize(BootstrapRegistry registry);
}

@ -0,0 +1,31 @@
/*
* 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;
/**
* A {@link BootstrapContext} that also provides configuration methods via the
* {@link BootstrapRegistry} interface.
*
* @author Phillip Webb
* @since 2.4.0
* @see BootstrapRegistry
* @see BootstrapContext
* @see DefaultBootstrapContext
*/
public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext {
}

@ -0,0 +1,104 @@
/*
* 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;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.util.Assert;
/**
* Default {@link ConfigurableBootstrapContext} implementation.
*
* @author Phillip Webb
* @since 2.4.0
*/
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
private final Map<Class<?>, InstanceSupplier<?>> instanceSuppliers = new HashMap<>();
private final Map<Class<?>, Object> instances = new HashMap<>();
private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster();
@Override
public <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier) {
register(type, instanceSupplier, true);
}
@Override
public <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier) {
register(type, instanceSupplier, false);
}
private <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier, boolean replaceExisting) {
Assert.notNull(type, "Type must not be null");
Assert.notNull(instanceSupplier, "InstanceSupplier must not be null");
synchronized (this.instanceSuppliers) {
boolean alreadyRegistered = this.instanceSuppliers.containsKey(type);
if (replaceExisting || !alreadyRegistered) {
Assert.state(!this.instances.containsKey(type), () -> type.getName() + " has already been created");
this.instanceSuppliers.put(type, instanceSupplier);
}
}
}
@Override
public <T> boolean isRegistered(Class<T> type) {
synchronized (this.instanceSuppliers) {
return this.instanceSuppliers.containsKey(type);
}
}
@Override
@SuppressWarnings("unchecked")
public <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type) {
synchronized (this.instanceSuppliers) {
return (InstanceSupplier<T>) this.instanceSuppliers.get(type);
}
}
@Override
public void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener) {
this.events.addApplicationListener(listener);
}
@Override
@SuppressWarnings("unchecked")
public <T> T get(Class<T> type) throws IllegalStateException {
synchronized (this.instanceSuppliers) {
InstanceSupplier<?> instanceSupplier = this.instanceSuppliers.get(type);
Assert.state(instanceSupplier != null, () -> type.getName() + " has not been registered");
return (T) this.instances.computeIfAbsent(type, (key) -> instanceSupplier.get(this));
}
}
/**
* Method to be called when {@link BootstrapContext} is closed and the
* {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
public void close(ConfigurableApplicationContext applicationContext) {
this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext));
}
}

@ -236,6 +236,8 @@ public class SpringApplication {
private Map<String, Object> defaultProperties;
private List<Bootstrapper> bootstrappers;
private Set<String> additionalProfiles = Collections.emptySet();
private boolean allowBeanDefinitionOverriding;
@ -278,6 +280,7 @@ public class SpringApplication {
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
@ -307,21 +310,22 @@ public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(this.mainApplicationClass);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class<?>[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
@ -346,13 +350,19 @@ public class SpringApplication {
return context;
}
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
return bootstrapContext;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment);
@ -375,12 +385,14 @@ public class SpringApplication {
}
}
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
@ -1028,6 +1040,17 @@ public class SpringApplication {
this.addConversionService = addConversionService;
}
/**
* Adds a {@link Bootstrapper} that can be used to initialize the
* {@link BootstrapRegistry}.
* @param bootstrapper the bootstraper
* @since 2.4.0
*/
public void addBootstrapper(Bootstrapper bootstrapper) {
Assert.notNull(bootstrapper, "Bootstrapper must not be null");
this.bootstrappers.add(bootstrapper);
}
/**
* Set default environment properties which will be used in addition to those in the
* existing {@link Environment}.

@ -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.
@ -38,15 +38,40 @@ public interface SpringApplicationRunListener {
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
* @param bootstrapContext the bootstrap context
*/
default void starting(ConfigurableBootstrapContext bootstrapContext) {
starting();
}
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
* @deprecated since 2.4.0 in favor of {@link #starting(ConfigurableBootstrapContext)}
*/
@Deprecated
default void starting() {
}
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param bootstrapContext the bootstrap context
* @param environment the environment
*/
default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
environmentPrepared(environment);
}
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
* @deprecated since 2.4.0 in favor of
* {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
*/
@Deprecated
default void environmentPrepared(ConfigurableEnvironment environment) {
}

@ -49,17 +49,18 @@ class SpringApplicationRunListeners {
this.applicationStartup = applicationStartup;
}
void starting(Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", SpringApplicationRunListener::starting, (step) -> {
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
void environmentPrepared(ConfigurableEnvironment environment) {
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(environment));
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
void contextPrepared(ConfigurableApplicationContext context) {

@ -30,6 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.Banner;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.Bootstrapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.convert.ApplicationConversionService;
@ -397,6 +399,18 @@ public class SpringApplicationBuilder {
return this;
}
/**
* Adds a {@link Bootstrapper} that can be used to initialize the
* {@link BootstrapRegistry}.
* @param bootstrapper the bootstraper
* @return the current builder
* @since 2.4.0
*/
public SpringApplicationBuilder addBootstrapper(Bootstrapper bootstrapper) {
this.application.addBootstrapper(bootstrapper);
return this;
}
/**
* Flag to control whether the application should be initialized lazily.
* @param lazyInitialization the flag to set. Defaults to false.

@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption;
import org.springframework.boot.context.properties.bind.BindException;
@ -33,7 +34,6 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -97,7 +97,7 @@ class ConfigDataEnvironment {
private final Log logger;
private final BootstrapRegistry bootstrapRegistry;
private final ConfigurableBootstrapContext bootstrapContext;
private final ConfigurableEnvironment environment;
@ -112,12 +112,12 @@ class ConfigDataEnvironment {
/**
* Create a new {@link ConfigDataEnvironment} instance.
* @param logFactory the deferred log factory
* @param bootstrapRegistry the bootstrap registry
* @param bootstrapContext the bootstrap context
* @param environment the Spring {@link Environment}.
* @param resourceLoader {@link ResourceLoader} to load resource locations
* @param additionalProfiles any additional profiles to activate
*/
ConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry,
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
@ -126,17 +126,20 @@ class ConfigDataEnvironment {
.orElse(ConfigDataLocationNotFoundAction.FAIL);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry;
this.bootstrapContext = bootstrapContext;
this.environment = environment;
this.resolvers = createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
this.additionalProfiles = additionalProfiles;
this.loaders = new ConfigDataLoaders(logFactory, locationNotFoundAction);
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, locationNotFoundAction);
this.contributors = createContributors(binder);
}
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
}
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
@ -159,7 +162,7 @@ class ConfigDataEnvironment {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, contributors);
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}
ConfigDataEnvironmentContributors getContributors() {

@ -28,6 +28,7 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.BindContext;
@ -37,7 +38,6 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.origin.Origin;
import org.springframework.core.log.LogMessage;
@ -56,25 +56,25 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
private final ConfigDataEnvironmentContributor root;
private final BootstrapRegistry bootstrapRegistry;
private final ConfigurableBootstrapContext bootstrapContext;
/**
* Create a new {@link ConfigDataEnvironmentContributors} instance.
* @param logFactory the log factory
* @param bootstrapRegistry the bootstrap registry
* @param bootstrapContext the bootstrap context
* @param contributors the initial set of contributors
*/
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry,
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry;
this.bootstrapContext = bootstrapContext;
this.root = ConfigDataEnvironmentContributor.of(contributors);
}
private ConfigDataEnvironmentContributors(Log logger, BootstrapRegistry bootstrapRegistry,
private ConfigDataEnvironmentContributors(Log logger, ConfigurableBootstrapContext bootstrapContext,
ConfigDataEnvironmentContributor root) {
this.logger = logger;
this.bootstrapRegistry = bootstrapRegistry;
this.bootstrapContext = bootstrapContext;
this.root = root;
}
@ -107,7 +107,7 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
result, activationContext, true);
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry,
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
@ -122,14 +122,14 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
+ imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet()));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry,
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
protected final BootstrapRegistry getBootstrapRegistry() {
return this.bootstrapRegistry;
protected final ConfigurableBootstrapContext getBootstrapContext() {
return this.bootstrapContext;
}
private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors,
@ -222,8 +222,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
}
@Override
public BootstrapRegistry getBootstrapRegistry() {
return this.contributors.getBootstrapRegistry();
public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapContext();
}
}
@ -264,8 +264,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
}
@Override
public BootstrapRegistry getBootstrapRegistry() {
return this.contributors.getBootstrapRegistry();
public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapContext();
}
@Override

@ -23,9 +23,9 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
@ -61,12 +61,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
private final Log logger;
private final BootstrapRegistry bootstrapRegistry;
private final ConfigurableBootstrapContext bootstrapContext;
public ConfigDataEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) {
public ConfigDataEnvironmentPostProcessor(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext) {
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry;
this.bootstrapContext = bootstrapContext;
}
@Override
@ -95,7 +96,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, environment, resourceLoader,
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles);
}
@ -125,13 +126,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
* directly and not necessarily as part of a {@link SpringApplication}.
* @param environment the environment to apply {@link ConfigData} to
* @param resourceLoader the resource loader to use
* @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a
* throw-away registry
* @param bootstrapContext the bootstrap context to use or {@code null} to use a
* throw-away context
* @param additionalProfiles any additional profiles that should be applied
*/
public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
BootstrapRegistry bootstrapRegistry, String... additionalProfiles) {
applyTo(environment, resourceLoader, bootstrapRegistry, Arrays.asList(additionalProfiles));
ConfigurableBootstrapContext bootstrapContext, String... additionalProfiles) {
applyTo(environment, resourceLoader, bootstrapContext, Arrays.asList(additionalProfiles));
}
/**
@ -140,16 +141,16 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
* directly and not necessarily as part of a {@link SpringApplication}.
* @param environment the environment to apply {@link ConfigData} to
* @param resourceLoader the resource loader to use
* @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a
* throw-away registry
* @param bootstrapContext the bootstrap context to use or {@code null} to use a
* throw-away context
* @param additionalProfiles any additional profiles that should be applied
*/
public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
BootstrapRegistry bootstrapRegistry, Collection<String> additionalProfiles) {
ConfigurableBootstrapContext bootstrapContext, Collection<String> additionalProfiles) {
DeferredLogFactory logFactory = Supplier::get;
bootstrapRegistry = (bootstrapRegistry != null) ? bootstrapRegistry : new DefaultBootstrapRegisty();
bootstrapContext = (bootstrapContext != null) ? bootstrapContext : new DefaultBootstrapContext();
ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory,
bootstrapRegistry);
bootstrapContext);
postProcessor.postProcessEnvironment(environment, resourceLoader, additionalProfiles);
}

@ -20,6 +20,10 @@ import java.io.IOException;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
/**
* Strategy class that can be used used to load {@link ConfigData} instances from a
* {@link ConfigDataLocation location}. Implementations should be added as a
@ -27,6 +31,9 @@ import org.apache.commons.logging.Log;
* supported:
* <ul>
* <li>{@link Log} - if the resolver needs deferred logging</li>
* <li>{@link ConfigurableBootstrapContext} - A bootstrap context that can be used to
* store objects that may be expensive to create, or need to be shared
* ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li>
* </ul>
* <p>
* Multiple loaders cannot claim the same location.

@ -16,7 +16,7 @@
package org.springframework.boot.context.config;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.env.EnvironmentPostProcessor;
/**
@ -28,10 +28,10 @@ import org.springframework.boot.env.EnvironmentPostProcessor;
public interface ConfigDataLoaderContext {
/**
* Provides access to the {@link BootstrapRegistry} shared across all
* Provides access to the {@link ConfigurableBootstrapContext} shared across all
* {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
* @return the bootstrap registry
* @return the bootstrap context
*/
BootstrapRegistry getBootstrapRegistry();
ConfigurableBootstrapContext getBootstrapContext();
}

@ -23,6 +23,9 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator;
import org.springframework.core.ResolvableType;
@ -48,27 +51,36 @@ class ConfigDataLoaders {
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param logFactory the deferred log factory
*/
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction) {
this(logFactory, locationNotFoundAction, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction) {
this(logFactory, bootstrapContext, locationNotFoundAction,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param names the {@link ConfigDataLoader} class names instantiate
*/
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction,
List<String> names) {
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, List<String> names) {
this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> availableParameters.add(Log.class, logFactory::getLog));
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
this.loaders = instantiator.instantiate(names);
this.locationTypes = getLocationTypes(this.loaders);
}

@ -21,6 +21,9 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@ -36,6 +39,9 @@ import org.springframework.core.io.ResourceLoader;
* <li>{@link Binder} - if the resolver needs to obtain values from the initial
* {@link Environment}</li>
* <li>{@link ResourceLoader} - if the resolver needs a resource loader</li>
* <li>{@link ConfigurableBootstrapContext} - A bootstrap context that can be used to
* store objects that may be expensive to create, or need to be shared
* ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li>
* </ul>
* <p>
* Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The

@ -16,8 +16,8 @@
package org.springframework.boot.context.config;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.origin.Origin;
@ -45,11 +45,11 @@ public interface ConfigDataLocationResolverContext {
ConfigDataLocation getParent();
/**
* Provides access to the {@link BootstrapRegistry} shared across all
* Provides access to the {@link ConfigurableBootstrapContext} shared across all
* {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
* @return the bootstrap registry
* @return the bootstrap context
*/
BootstrapRegistry getBootstrapRegistry();
ConfigurableBootstrapContext getBootstrapContext();
/**
* Return the {@link Origin} of a location that's being resolved.

@ -23,6 +23,9 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator;
@ -50,28 +53,31 @@ class ConfigDataLocationResolvers {
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param bootstrapContext the bootstrap context
* @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, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader) {
this(logFactory, locationNotFoundAction, binder, resourceLoader,
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
this(logFactory, bootstrapContext, 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 bootstrapContext the bootstrap context
* @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, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader, List<String> names) {
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader,
List<String> names) {
this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
@ -79,6 +85,9 @@ class ConfigDataLocationResolvers {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(Binder.class, binder);
availableParameters.add(ResourceLoader.class, resourceLoader);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
this.resolvers = reorder(instantiator.instantiate(names));
}

@ -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,6 +16,7 @@
package org.springframework.boot.context.event;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -30,6 +31,8 @@ import org.springframework.core.env.Environment;
@SuppressWarnings("serial")
public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent {
private final ConfigurableBootstrapContext bootstrapContext;
private final ConfigurableEnvironment environment;
/**
@ -37,13 +40,38 @@ public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent
* @param application the current application
* @param args the arguments the application is running with
* @param environment the environment that was just created
* @deprecated since 2.4.0 in favor of
* {@link #ApplicationEnvironmentPreparedEvent(ConfigurableBootstrapContext, SpringApplication, String[], ConfigurableEnvironment)}
*/
@Deprecated
public ApplicationEnvironmentPreparedEvent(SpringApplication application, String[] args,
ConfigurableEnvironment environment) {
this(null, application, args, environment);
}
/**
* Create a new {@link ApplicationEnvironmentPreparedEvent} instance.
* @param bootstrapContext the bootstrap context
* @param application the current application
* @param args the arguments the application is running with
* @param environment the environment that was just created
*/
public ApplicationEnvironmentPreparedEvent(ConfigurableBootstrapContext bootstrapContext,
SpringApplication application, String[] args, ConfigurableEnvironment environment) {
super(application, args);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
}
/**
* Return the bootstap context.
* @return the bootstrap context
* @since 2.4.0
*/
public ConfigurableBootstrapContext getBootstrapContext() {
return this.bootstrapContext;
}
/**
* Return the environment.
* @return the environment

@ -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,6 +16,7 @@
package org.springframework.boot.context.event;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
@ -35,13 +36,39 @@ import org.springframework.core.env.Environment;
@SuppressWarnings("serial")
public class ApplicationStartingEvent extends SpringApplicationEvent {
private final ConfigurableBootstrapContext bootstrapContext;
/**
* Create a new {@link ApplicationStartingEvent} instance.
* @param application the current application
* @param args the arguments the application is running with
* @deprecated since 2.4.0 in favor of
* {@link #ApplicationStartingEvent(ConfigurableBootstrapContext, SpringApplication, String[])}
*/
@Deprecated
public ApplicationStartingEvent(SpringApplication application, String[] args) {
this(null, application, args);
}
/**
* Create a new {@link ApplicationStartingEvent} instance.
* @param bootstrapContext the bootstrap context
* @param application the current application
* @param args the arguments the application is running with
*/
public ApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext, SpringApplication application,
String[] args) {
super(application, args);
this.bootstrapContext = bootstrapContext;
}
/**
* Return the bootstap context.
* @return the bootstrap context
* @since 2.4.0
*/
public ConfigurableBootstrapContext getBootstrapContext() {
return this.bootstrapContext;
}
}

@ -19,6 +19,7 @@ package org.springframework.boot.context.event;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.availability.AvailabilityChangeEvent;
@ -70,14 +71,16 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
@Override

@ -1,123 +0,0 @@
/*
* 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.env;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
/**
* A simple object registry that is available during {@link Environment} post-processing
* up to the point that the {@link ApplicationContext} is prepared. The registry can be
* used to store objects that may be expensive to create, or need to be shared by
* different {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
* <p>
* The registry uses the object type as a key, meaning that only a single instance of a
* given class can be stored.
* <p>
* Registered instances may optionally use
* {@link Registration#onApplicationContextPrepared(BiConsumer)
* onApplicationContextPrepared(...)} to perform an action when the
* {@link ApplicationContext} is {@link ApplicationPreparedEvent prepared}. For example,
* an instance may choose to register itself as a regular Spring bean so that it is
* available for the application to use.
*
* @author Phillip Webb
* @since 2.4.0
* @see EnvironmentPostProcessor
*/
public interface BootstrapRegistry {
/**
* Get an instance from the registry, creating one if it does not already exist.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier a supplier used to create the instance if it doesn't
* already exist
* @return the registered instance
*/
<T> T get(Class<T> type, Supplier<T> instanceSupplier);
/**
* Get an instance from the registry, creating one if it does not already exist.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier a supplier used to create the instance if it doesn't
* already exist
* @param onApplicationContextPreparedAction the action that should be called when the
* application context is prepared. This action is ignored if the registration already
* exists.
* @return the registered instance
*/
<T> T get(Class<T> type, Supplier<T> instanceSupplier,
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction);
/**
* Register an instance with the registry and return a {@link Registration} that can
* be used to provide further configuration. This method will replace any existing
* registration.
* @param <T> the instance type
* @param type the instance type
* @param instanceSupplier a supplier used to create the instance if it doesn't
* already exist
* @return an instance registration
*/
<T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier);
/**
* Return if a registration exists for the given type.
* @param <T> the instance type
* @param type the instance type
* @return {@code true} if the type has already been registered
*/
<T> boolean isRegistered(Class<T> type);
/**
* Return any existing {@link Registration} for the given type.
* @param <T> the instance type
* @param type the instance type
* @return the existing registration or {@code null}
*/
<T> Registration<T> getRegistration(Class<T> type);
/**
* A single registration contained in the registry.
*
* @param <T> the instance type
*/
interface Registration<T> {
/**
* Get or crearte the registered object instance.
* @return the object instance
*/
T get();
/**
* Add an action that should run when the {@link ApplicationContext} has been
* prepared.
* @param action the action to run
*/
void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action);
}
}

@ -1,134 +0,0 @@
/*
* 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.env;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Default implementation of {@link BootstrapRegistry}.
*
* @author Phillip Webb
* @since 2.4.0
*/
public class DefaultBootstrapRegisty implements BootstrapRegistry {
private final Map<Class<?>, DefaultRegistration<?>> registrations = new HashMap<>();
@Override
public <T> T get(Class<T> type, Supplier<T> instanceSupplier) {
return get(type, instanceSupplier, null);
}
@Override
public <T> T get(Class<T> type, Supplier<T> instanceSupplier,
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction) {
Registration<T> registration = getRegistration(type);
if (registration != null) {
return registration.get();
}
registration = register(type, instanceSupplier);
registration.onApplicationContextPrepared(onApplicationContextPreparedAction);
return registration.get();
}
@Override
public <T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier) {
DefaultRegistration<T> registration = new DefaultRegistration<>(instanceSupplier);
this.registrations.put(type, registration);
return registration;
}
@Override
public <T> boolean isRegistered(Class<T> type) {
return getRegistration(type) != null;
}
@Override
@SuppressWarnings("unchecked")
public <T> Registration<T> getRegistration(Class<T> type) {
return (Registration<T>) this.registrations.get(type);
}
/**
* Method to be called when the {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
public void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
this.registrations.values()
.forEach((registration) -> registration.applicationContextPrepared(applicationContext));
}
/**
* Clear the registry to reclaim memory.
*/
public void clear() {
this.registrations.clear();
}
/**
* Default implementation of {@link Registration}.
*/
private static class DefaultRegistration<T> implements Registration<T> {
private Supplier<T> instanceSupplier;
private volatile T instance;
private List<BiConsumer<ConfigurableApplicationContext, T>> applicationContextPreparedActions = new ArrayList<>();
DefaultRegistration(Supplier<T> instanceSupplier) {
this.instanceSupplier = instanceSupplier;
}
@Override
public T get() {
T instance = this.instance;
if (instance == null) {
synchronized (this.instanceSupplier) {
instance = this.instanceSupplier.get();
this.instance = instance;
}
}
return instance;
}
@Override
public void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action) {
if (action != null) {
this.applicationContextPreparedActions.add(action);
}
}
/**
* Method called when the {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
this.applicationContextPreparedActions.forEach((consumer) -> consumer.accept(applicationContext, get()));
}
}
}

@ -18,6 +18,9 @@ package org.springframework.boot.env;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
@ -41,8 +44,9 @@ import org.springframework.core.env.Environment;
* itself to configure logging levels).</li>
* <li>{@link Log} - A log with output deferred until the application has been full
* prepared (allowing the environment itself to configure logging levels).</li>
* <li>{@link BootstrapRegistry} - A bootstrap registry that can be used to store objects
* that may be expensive to create, or need to be shared.</li>
* <li>{@link ConfigurableBootstrapContext} - A bootstrap context that can be used to
* store objects that may be expensive to create, or need to be shared
* ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li>
* </ul>
*
* @author Andy Wilkinson

@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.util.List;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
@ -44,8 +45,6 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private final DeferredLogs deferredLogs;
private final DefaultBootstrapRegisty bootstrapRegistry;
private int order = DEFAULT_ORDER;
private final EnvironmentPostProcessorsFactory postProcessorsFactory;
@ -65,14 +64,13 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
* @param postProcessorsFactory the post processors factory
*/
public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) {
this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty());
this(postProcessorsFactory, new DeferredLogs());
}
EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory,
DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) {
DeferredLogs deferredLogs) {
this.postProcessorsFactory = postProcessorsFactory;
this.deferredLogs = deferredLogs;
this.bootstrapRegistry = bootstrapRegistry;
}
@Override
@ -98,13 +96,12 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors()) {
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext());
finish();
}
@ -113,12 +110,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
}
private void finish() {
this.bootstrapRegistry.clear();
this.deferredLogs.switchOverAll();
}
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() {
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry);
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ConfigurableBootstrapContext bootstrapContext) {
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
}
@Override

@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.util.List;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.io.support.SpringFactoriesLoader;
@ -34,11 +35,11 @@ public interface EnvironmentPostProcessorsFactory {
/**
* Create all requested {@link EnvironmentPostProcessor} instances.
* @param logFactory a deferred log factory
* @param bootstrapRegistry a bootstrap registry
* @param bootstrapContext a bootstrap context
* @return the post processor instances
*/
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
BootstrapRegistry bootstrapRegistry);
ConfigurableBootstrapContext bootstrapContext);
/**
* Return a {@link EnvironmentPostProcessorsFactory} backed by

@ -21,6 +21,9 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator;
@ -48,12 +51,14 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce
@Override
public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
BootstrapRegistry bootstrapRegistry) {
ConfigurableBootstrapContext bootstrapContext) {
Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,
(parameters) -> {
parameters.add(DeferredLogFactory.class, logFactory);
parameters.add(Log.class, logFactory::getLog);
parameters.add(BootstrapRegistry.class, bootstrapRegistry);
parameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
parameters.add(BootstrapContext.class, bootstrapContext);
parameters.add(BootstrapRegistry.class, bootstrapContext);
});
return instantiator.instantiate(this.classNames);
}

@ -0,0 +1,223 @@
/*
* 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;
import java.util.concurrent.atomic.AtomicInteger;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AssertProvider;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link DefaultBootstrapContext}.
*
* @author Phillip Webb
*/
class DefaultBootstrapContextTests {
private DefaultBootstrapContext context = new DefaultBootstrapContext();
private AtomicInteger counter = new AtomicInteger();
private StaticApplicationContext applicationContext = new StaticApplicationContext();
@Test
void registerWhenTypeIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(null, InstanceSupplier.of(1)))
.withMessage("Type must not be null");
}
@Test
void registerWhenRegistrationIsNullThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(Integer.class, null))
.withMessage("InstanceSupplier must not be null");
}
@Test
void registerWhenNotAlreadyRegisteredRegistersInstance() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
assertThat(this.context.get(Integer.class)).isEqualTo(0);
assertThat(this.context.get(Integer.class)).isEqualTo(0);
}
@Test
void registerWhenAlreadyRegisteredRegistersReplacedInstance() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
this.context.register(Integer.class, InstanceSupplier.of(100));
assertThat(this.context.get(Integer.class)).isEqualTo(100);
assertThat(this.context.get(Integer.class)).isEqualTo(100);
}
@Test
void registerWhenAlreadyCreatedThrowsException() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
this.context.get(Integer.class);
assertThatIllegalStateException()
.isThrownBy(() -> this.context.register(Integer.class, InstanceSupplier.of(100)))
.withMessage("java.lang.Integer has already been created");
}
@Test
void registerWithDependencyRegistersInstance() {
this.context.register(Integer.class, InstanceSupplier.of(100));
this.context.register(String.class, this::integerAsString);
assertThat(this.context.get(String.class)).isEqualTo("100");
}
private String integerAsString(BootstrapContext context) {
return String.valueOf(context.get(Integer.class));
}
@Test
void registerIfAbsentWhenAbsentRegisters() {
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L));
assertThat(this.context.get(Long.class)).isEqualTo(100L);
}
@Test
void registerIfAbsentWhenPresentDoesNotRegister() {
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(1L));
this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L));
assertThat(this.context.get(Long.class)).isEqualTo(1L);
}
@Test
void isRegisteredWhenNotRegisteredReturnsFalse() {
this.context.register(Number.class, InstanceSupplier.of(1));
assertThat(this.context.isRegistered(Long.class)).isFalse();
}
@Test
void isRegisteredWhenRegisteredReturnsTrue() {
this.context.register(Number.class, InstanceSupplier.of(1));
assertThat(this.context.isRegistered(Number.class)).isTrue();
}
@Test
void getRegisteredInstanceSupplierWhenNotRegisteredReturnsNull() {
this.context.register(Number.class, InstanceSupplier.of(1));
assertThat(this.context.getRegisteredInstanceSupplier(Long.class)).isNull();
}
@Test
void getRegisteredInstanceSupplierWhenRegisteredReturnsRegistration() {
InstanceSupplier<Number> instanceSupplier = InstanceSupplier.of(1);
this.context.register(Number.class, instanceSupplier);
assertThat(this.context.getRegisteredInstanceSupplier(Number.class)).isSameAs(instanceSupplier);
}
@Test
void getWhenNoRegistrationThrowsIllegalStateException() {
this.context.register(Number.class, InstanceSupplier.of(1));
assertThatIllegalStateException().isThrownBy(() -> this.context.get(Long.class))
.withMessageContaining("has not been registered");
}
@Test
void getWhenRegisteredAsNullReturnsNull() {
this.context.register(Number.class, InstanceSupplier.of(null));
assertThat(this.context.get(Number.class)).isNull();
}
@Test
void getCreatesOnlyOneInstance() {
this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement));
assertThat(this.context.get(Integer.class)).isEqualTo(0);
assertThat(this.context.get(Integer.class)).isEqualTo(0);
}
@Test
void closeMulticastsEventToListeners() {
TestCloseListener listener = new TestCloseListener();
this.context.addCloseListener(listener);
assertThat(listener).wasNotCalled();
this.context.close(this.applicationContext);
assertThat(listener).wasCalledOnlyOnce().hasBootstrapContextSameAs(this.context)
.hasApplicationContextSameAs(this.applicationContext);
}
@Test
void addCloseListenerIgnoresMultipleCallsWithSameListener() {
TestCloseListener listener = new TestCloseListener();
this.context.addCloseListener(listener);
this.context.addCloseListener(listener);
this.context.close(this.applicationContext);
assertThat(listener).wasCalledOnlyOnce();
}
private static class TestCloseListener
implements ApplicationListener<BootstrapContextClosedEvent>, AssertProvider<CloseListenerAssert> {
private int called;
private BootstrapContext bootstrapContext;
private ConfigurableApplicationContext applicationContext;
@Override
public void onApplicationEvent(BootstrapContextClosedEvent event) {
this.called++;
this.bootstrapContext = event.getBootstrapContext();
this.applicationContext = event.getApplicationContext();
}
@Override
public CloseListenerAssert assertThat() {
return new CloseListenerAssert(this);
}
}
private static class CloseListenerAssert extends AbstractAssert<CloseListenerAssert, TestCloseListener> {
CloseListenerAssert(TestCloseListener actual) {
super(actual, CloseListenerAssert.class);
}
CloseListenerAssert wasCalledOnlyOnce() {
assertThat(this.actual.called).as("action calls").isEqualTo(1);
return this;
}
CloseListenerAssert wasNotCalled() {
assertThat(this.actual.called).as("action calls").isEqualTo(0);
return this;
}
CloseListenerAssert hasBootstrapContextSameAs(BootstrapContext bootstrapContext) {
assertThat(this.actual.bootstrapContext).isSameAs(bootstrapContext);
return this;
}
CloseListenerAssert hasApplicationContextSameAs(ApplicationContext applicationContext) {
assertThat(this.actual.applicationContext).isSameAs(applicationContext);
return this;
}
}
}

@ -48,6 +48,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.LivenessState;
@ -105,6 +106,8 @@ import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;
@ -1201,6 +1204,35 @@ class SpringApplicationTests {
assertThat(startCount).isEqualTo(endCount);
}
@Test
void addBootstrapper() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.addBootstrapper(
(bootstrapContext) -> bootstrapContext.register(String.class, InstanceSupplier.of("boot")));
TestApplicationListener listener = new TestApplicationListener();
application.addListeners(listener);
application.run();
ApplicationStartingEvent startingEvent = listener.getEvent(ApplicationStartingEvent.class);
assertThat(startingEvent.getBootstrapContext().get(String.class));
ApplicationEnvironmentPreparedEvent environmentPreparedEvent = listener
.getEvent(ApplicationEnvironmentPreparedEvent.class);
assertThat(environmentPreparedEvent.getBootstrapContext().get(String.class));
}
@Test
void addBootstrapperCanRegisterBeans() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.addBootstrapper((bootstrapContext) -> {
bootstrapContext.register(String.class, InstanceSupplier.of("boot"));
bootstrapContext.addCloseListener((event) -> event.getApplicationContext().getBeanFactory()
.registerSingleton("test", event.getBootstrapContext().get(String.class)));
});
ConfigurableApplicationContext applicationContext = application.run();
assertThat(applicationContext.getBean("test")).isEqualTo("boot");
}
private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(
S state) {
return (argument) -> (argument instanceof AvailabilityChangeEvent<?>)
@ -1658,4 +1690,20 @@ class SpringApplicationTests {
}
static class TestApplicationListener implements ApplicationListener<ApplicationEvent> {
private final MultiValueMap<Class<?>, ApplicationEvent> events = new LinkedMultiValueMap<>();
@Override
public void onApplicationEvent(ApplicationEvent event) {
this.events.add(event.getClass(), event);
}
@SuppressWarnings("unchecked")
<E extends ApplicationEvent> E getEvent(Class<E> type) {
return (E) this.events.get(type).get(0);
}
}
}

@ -282,6 +282,15 @@ class SpringApplicationBuilderTests {
this.context.getBean(ChildConfig.class);
}
@Test
void addBootstrapper() {
SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class)
.web(WebApplicationType.NONE).addBootstrapper((context) -> context.addCloseListener(
(event) -> event.getApplicationContext().getBeanFactory().registerSingleton("test", "spring")));
this.context = application.run();
assertThat(this.context.getBean("test")).isEqualTo("spring");
}
@Configuration(proxyBeanMethods = false)
static class ExampleConfig {

@ -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,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
@ -123,7 +124,8 @@ class ApplicationPidFileWriterTests {
File file = new File(this.tempDir, "pid");
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
listener.setTriggerEventType(ApplicationStartingEvent.class);
listener.onApplicationEvent(new ApplicationStartingEvent(new SpringApplication(), new String[] {}));
listener.onApplicationEvent(
new ApplicationStartingEvent(new DefaultBootstrapContext(), new SpringApplication(), new String[] {}));
assertThat(contentOf(file)).isNotEmpty();
}
@ -170,7 +172,8 @@ class ApplicationPidFileWriterTests {
private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) {
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
return new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[] {}, environment);
return new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(), new SpringApplication(),
new String[] {}, environment);
}
private SpringApplicationEvent createPreparedEvent(String propName, String propValue) {

@ -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.
@ -18,6 +18,7 @@ package org.springframework.boot.context;
import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
@ -40,7 +41,7 @@ class FileEncodingApplicationListenerTests {
private final ConfigurableEnvironment environment = new StandardEnvironment();
private final ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(
new SpringApplication(), new String[0], this.environment);
new DefaultBootstrapContext(), new SpringApplication(), new String[0], this.environment);
@Test
void testIllegalState() {

@ -30,13 +30,12 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
@ -60,7 +59,7 @@ class ConfigDataEnvironmentContributorsTests {
private DeferredLogFactory logFactory = Supplier::get;
private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private MockEnvironment environment;
@ -80,9 +79,10 @@ class ConfigDataEnvironmentContributorsTests {
void setup() {
this.environment = new MockEnvironment();
this.binder = Binder.get(this.environment);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, null);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL);
this.importer = new ConfigDataImporter(resolvers, loaders);
this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null);
}
@ -91,7 +91,7 @@ class ConfigDataEnvironmentContributorsTests {
void createCreatesWithInitialContributors() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
Iterator<ConfigDataEnvironmentContributor> iterator = contributors.iterator();
assertThat(iterator.next()).isSameAs(contributor);
assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT);
@ -102,7 +102,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofExisting(new MockPropertySource());
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
assertThat(withProcessedImports).isSameAs(contributors);
@ -119,7 +119,7 @@ class ConfigDataEnvironmentContributorsTests {
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
Iterator<ConfigDataEnvironmentContributor> iterator = withProcessedImports.iterator();
@ -148,7 +148,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
Iterator<ConfigDataEnvironmentContributor> iterator = withProcessedImports.iterator();
@ -174,7 +174,7 @@ class ConfigDataEnvironmentContributorsTests {
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(existingContributor, contributor));
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
@ -200,7 +200,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations));
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
@ -222,11 +222,11 @@ class ConfigDataEnvironmentContributorsTests {
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(existingContributor, contributor));
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry);
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
@Test
@ -244,11 +244,11 @@ class ConfigDataEnvironmentContributorsTests {
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(existingContributor, contributor));
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any());
ConfigDataLoaderContext context = this.loaderContext.getValue();
assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry);
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
@Test
@ -257,7 +257,7 @@ class ConfigDataEnvironmentContributorsTests {
propertySource.setProperty("test", "springboot");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot");
}
@ -272,7 +272,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("one");
}
@ -288,7 +288,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("two");
}
@ -300,7 +300,7 @@ class ConfigDataEnvironmentContributorsTests {
propertySource.setProperty("other", "springboot");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(contributor));
this.bootstrapContext, Arrays.asList(contributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot");
}
@ -317,7 +317,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("two");
}
@ -333,7 +333,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class))
.satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class));
@ -350,7 +350,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class))
.satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class));
@ -368,7 +368,7 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
this.bootstrapContext, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class))
.satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class));

@ -19,10 +19,10 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.TestConfigDataBootstrap.LoaderHelper;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

@ -27,8 +27,8 @@ import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
@ -60,7 +60,7 @@ class ConfigDataEnvironmentPostProcessorTests {
@Spy
private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get,
new DefaultBootstrapRegisty());
new DefaultBootstrapContext());
@Captor
private ArgumentCaptor<Set<String>> additionalProfilesCaptor;

@ -26,11 +26,11 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
@ -52,7 +52,7 @@ class ConfigDataEnvironmentTests {
private DeferredLogFactory logFactory = Supplier::get;
private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private MockEnvironment environment = new MockEnvironment();
@ -64,7 +64,7 @@ class ConfigDataEnvironmentTests {
void createWhenUseLegacyPropertyInEnvironmentThrowsException() {
this.environment.setProperty("spring.config.use-legacy-processing", "true");
assertThatExceptionOfType(UseLegacyConfigProcessingException.class)
.isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, this.environment,
.isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment,
this.resourceLoader, this.additionalProfiles));
}
@ -72,7 +72,7 @@ class ConfigDataEnvironmentTests {
void createExposesEnvironmentBinderToConfigDataLocationResolvers() {
this.environment.setProperty("spring", "boot");
TestConfigDataEnvironment configDataEnvironment = new TestConfigDataEnvironment(this.logFactory,
this.bootstrapRegistry, this.environment, this.resourceLoader, this.additionalProfiles);
this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles);
assertThat(configDataEnvironment.getConfigDataLocationResolversBinder().bind("spring", String.class).get())
.isEqualTo("boot");
}
@ -85,7 +85,7 @@ class ConfigDataEnvironmentTests {
this.environment.getPropertySources().addLast(propertySource1);
this.environment.getPropertySources().addLast(propertySource2);
this.environment.getPropertySources().addLast(propertySource3);
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
@ -104,7 +104,7 @@ class ConfigDataEnvironmentTests {
this.environment.getPropertySources().addLast(defaultPropertySource);
this.environment.getPropertySources().addLast(propertySource1);
this.environment.getPropertySources().addLast(propertySource2);
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
@ -120,7 +120,7 @@ class ConfigDataEnvironmentTests {
this.environment.setProperty("spring.config.location", "l1,l2");
this.environment.setProperty("spring.config.additional-location", "a1,a2");
this.environment.setProperty("spring.config.import", "i1,i2");
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
@ -132,7 +132,7 @@ class ConfigDataEnvironmentTests {
@Test
void processAndApplyAddsImportedSourceToEnvironment(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
assertThat(this.environment.getProperty("spring")).isEqualTo("boot");
@ -141,7 +141,7 @@ class ConfigDataEnvironmentTests {
@Test
void processAndApplyOnlyAddsActiveContributors(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
assertThat(this.environment.getProperty("spring")).isEqualTo("boot");
@ -153,7 +153,7 @@ class ConfigDataEnvironmentTests {
MockPropertySource defaultPropertySource = new MockPropertySource("defaultProperties");
this.environment.getPropertySources().addFirst(defaultPropertySource);
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
List<PropertySource<?>> sources = this.environment.getPropertySources().stream().collect(Collectors.toList());
@ -163,7 +163,7 @@ class ConfigDataEnvironmentTests {
@Test
void processAndApplySetsDefaultProfiles(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
assertThat(this.environment.getDefaultProfiles()).containsExactly("one", "two", "three");
@ -172,7 +172,7 @@ class ConfigDataEnvironmentTests {
@Test
void processAndApplySetsActiveProfiles(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
assertThat(this.environment.getActiveProfiles()).containsExactly("one", "two", "three");
@ -181,7 +181,7 @@ class ConfigDataEnvironmentTests {
@Test
void processAndApplySetsActiveProfilesAndProfileGroups(TestInfo info) {
this.environment.setProperty("spring.config.location", getConfigLocation(info));
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
configDataEnvironment.processAndApply();
assertThat(this.environment.getActiveProfiles()).containsExactly("one", "four", "five", "two", "three");
@ -191,7 +191,7 @@ class ConfigDataEnvironmentTests {
@Disabled("Disabled until spring.profiles support is dropped")
void processAndApplyWhenHasInvalidPropertyThrowsException() {
this.environment.setProperty("spring.profile", "a");
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry,
ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext,
this.environment, this.resourceLoader, this.additionalProfiles);
assertThatExceptionOfType(InvalidConfigDataPropertyException.class)
.isThrownBy(() -> configDataEnvironment.processAndApply());
@ -206,17 +206,19 @@ class ConfigDataEnvironmentTests {
private Binder configDataLocationResolversBinder;
TestConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry,
TestConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
super(logFactory, bootstrapRegistry, environment, resourceLoader, additionalProfiles);
super(logFactory, bootstrapContext, environment, resourceLoader, additionalProfiles);
}
@Override
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction,
Binder binder, ResourceLoader resourceLoader) {
this.configDataLocationResolversBinder = binder;
return super.createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader);
return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
}
Binder getConfigDataLocationResolversBinder() {

@ -24,6 +24,11 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockPropertySource;
@ -42,19 +47,28 @@ class ConfigDataLoadersTests {
private DeferredLogFactory logFactory = Supplier::get;
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class);
@Test
void createWhenLoaderHasLogParameterInjectsLog() {
new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName()));
}
@Test
void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() {
new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(BootstrappingConfigDataLoader.class.getName()));
assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot");
}
@Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
}
@ -62,7 +76,8 @@ class ConfigDataLoadersTests {
@Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for location test");
@ -71,8 +86,8 @@ class ConfigDataLoadersTests {
@Test
void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for location 'test'");
}
@ -80,7 +95,8 @@ class ConfigDataLoadersTests {
@Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL,
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
@ -131,6 +147,24 @@ class ConfigDataLoadersTests {
}
static class BootstrappingConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> {
BootstrappingConfigDataLoader(ConfigurableBootstrapContext configurableBootstrapContext,
BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) {
assertThat(configurableBootstrapContext).isNotNull();
assertThat(bootstrapRegistry).isNotNull();
assertThat(bootstrapContext).isNotNull();
assertThat(configurableBootstrapContext).isEqualTo(bootstrapRegistry).isEqualTo(bootstrapContext);
bootstrapRegistry.register(String.class, InstanceSupplier.of("boot"));
}
@Override
public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException {
throw new AssertionError("Unexpected call");
}
}
static class TestConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> {
@Override

@ -28,6 +28,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
@ -50,6 +55,8 @@ class ConfigDataLocationResolversTests {
private DeferredLogFactory logFactory = Supplier::get;
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Mock
private Binder binder;
@ -63,7 +70,7 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
@ -73,17 +80,24 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenNotInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class);
}
@Test
void createWhenResolverHasBootstrapParametersInjectsBootstrapContext() {
new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL,
this.binder, this.resourceLoader, Collections.singletonList(TestBootstrappingResolver.class.getName()));
assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot");
}
@Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory,
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
@ -95,7 +109,7 @@ class ConfigDataLocationResolversTests {
names.add(TestResolver.class.getName());
names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.class.getName());
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
@ -104,7 +118,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllResolvesUsingFirstSupportedResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
@ -118,7 +132,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllWhenProfileMergesResolvedLocations() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
@ -136,7 +150,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveWhenNoResolverThrowsException() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory,
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
@ -181,6 +195,19 @@ class ConfigDataLocationResolversTests {
}
static class TestBootstrappingResolver extends TestResolver {
TestBootstrappingResolver(ConfigurableBootstrapContext configurableBootstrapContext,
BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) {
assertThat(configurableBootstrapContext).isNotNull();
assertThat(bootstrapRegistry).isNotNull();
assertThat(bootstrapContext).isNotNull();
assertThat(configurableBootstrapContext).isEqualTo(bootstrapRegistry).isEqualTo(bootstrapContext);
bootstrapRegistry.register(String.class, InstanceSupplier.of("boot"));
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class HighestTestResolver extends TestResolver {

@ -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.
@ -19,6 +19,7 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
@ -53,8 +54,8 @@ class DelegatingApplicationListenerTests {
void orderedInitialize() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"context.listener.classes=" + MockInitB.class.getName() + "," + MockInitA.class.getName());
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0],
this.context.getEnvironment()));
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
new SpringApplication(), new String[0], this.context.getEnvironment()));
this.context.getBeanFactory().registerSingleton("testListener", this.listener);
this.context.refresh();
assertThat(this.context.getBeanFactory().getSingleton("a")).isEqualTo("a");
@ -63,15 +64,15 @@ class DelegatingApplicationListenerTests {
@Test
void noInitializers() {
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0],
this.context.getEnvironment()));
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
new SpringApplication(), new String[0], this.context.getEnvironment()));
}
@Test
void emptyInitializers() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "context.listener.classes:");
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0],
this.context.getEnvironment()));
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
new SpringApplication(), new String[0], this.context.getEnvironment()));
}
@Order(Ordered.HIGHEST_PRECEDENCE)

@ -20,7 +20,9 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.boot.BootstrapContextClosedEvent;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.MapPropertySource;
/**
@ -42,8 +44,9 @@ class TestConfigDataBootstrap {
@Override
public List<Location> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) {
ResolverHelper helper = context.getBootstrapRegistry().get(ResolverHelper.class,
() -> new ResolverHelper(location));
context.getBootstrapContext().registerIfAbsent(ResolverHelper.class,
InstanceSupplier.from(() -> new ResolverHelper(location)));
ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class);
return Collections.singletonList(new Location(helper));
}
@ -53,8 +56,10 @@ class TestConfigDataBootstrap {
@Override
public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException {
context.getBootstrapRegistry().get(LoaderHelper.class, () -> new LoaderHelper(location),
LoaderHelper::addToContext);
context.getBootstrapContext().registerIfAbsent(LoaderHelper.class,
InstanceSupplier.from(() -> new LoaderHelper(location)));
LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class);
context.getBootstrapContext().addCloseListener(helper);
return new ConfigData(
Collections.singleton(new MapPropertySource("loaded", Collections.singletonMap("test", "test"))));
}
@ -94,7 +99,7 @@ class TestConfigDataBootstrap {
}
static class LoaderHelper {
static class LoaderHelper implements ApplicationListener<BootstrapContextClosedEvent> {
private final Location location;
@ -106,8 +111,9 @@ class TestConfigDataBootstrap {
return this.location;
}
static void addToContext(ConfigurableApplicationContext context, LoaderHelper loaderHelper) {
context.getBeanFactory().registerSingleton("loaderHelper", loaderHelper);
@Override
public void onApplicationEvent(BootstrapContextClosedEvent event) {
event.getApplicationContext().getBeanFactory().registerSingleton("loaderHelper", this);
}
}

@ -25,6 +25,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.context.ApplicationEvent;
@ -42,6 +43,8 @@ import static org.mockito.Mockito.mock;
*/
class EventPublishingRunListenerTests {
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private SpringApplication application;
private EventPublishingRunListener runListener;
@ -60,9 +63,9 @@ class EventPublishingRunListenerTests {
void shouldPublishLifecycleEvents() {
StaticApplicationContext context = new StaticApplicationContext();
assertThat(this.eventListener.receivedEvents()).isEmpty();
this.runListener.starting();
this.runListener.starting(this.bootstrapContext);
checkApplicationEvents(ApplicationStartingEvent.class);
this.runListener.environmentPrepared(null);
this.runListener.environmentPrepared(this.bootstrapContext, null);
checkApplicationEvents(ApplicationEnvironmentPreparedEvent.class);
this.runListener.contextPrepared(context);
checkApplicationEvents(ApplicationContextInitializedEvent.class);

@ -41,6 +41,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
@ -97,6 +98,8 @@ class LoggingApplicationListenerTests {
private final ch.qos.logback.classic.Logger logger = this.loggerContext.getLogger(getClass());
private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private final SpringApplication springApplication = new SpringApplication();
private final GenericApplicationContext context = new GenericApplicationContext();
@ -113,7 +116,7 @@ class LoggingApplicationListenerTests {
this.output = output;
this.logFile = new File(this.tempDir.toFile(), "foo.log");
LogManager.getLogManager().readConfiguration(JavaLoggingSystem.class.getResourceAsStream("logging.properties"));
multicastEvent(new ApplicationStartingEvent(new SpringApplication(), NO_ARGS));
multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS));
new File(this.tempDir.toFile(), "spring.log").delete();
ConfigurableEnvironment environment = this.context.getEnvironment();
ConfigurationPropertySources.attach(environment);
@ -372,7 +375,8 @@ class LoggingApplicationListenerTests {
void parseArgsDoesntReplace() {
this.initializer.setSpringBootLogging(LogLevel.ERROR);
this.initializer.setParseArgs(false);
multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[] { "--debug" }));
multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication,
new String[] { "--debug" }));
this.initializer.initialize(this.context.getEnvironment(), this.context.getClassLoader());
this.logger.debug("testatdebug");
assertThat(this.output).doesNotContain("testatdebug");
@ -406,7 +410,7 @@ class LoggingApplicationListenerTests {
void shutdownHookIsNotRegisteredByDefault() {
TestLoggingApplicationListener listener = new TestLoggingApplicationListener();
System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName());
multicastEvent(listener, new ApplicationStartingEvent(new SpringApplication(), NO_ARGS));
multicastEvent(listener, new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS));
listener.initialize(this.context.getEnvironment(), this.context.getClassLoader());
assertThat(listener.shutdownHook).isNull();
}
@ -416,7 +420,7 @@ class LoggingApplicationListenerTests {
TestLoggingApplicationListener listener = new TestLoggingApplicationListener();
System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName());
addPropertiesToEnvironment(this.context, "logging.register_shutdown_hook=true");
multicastEvent(listener, new ApplicationStartingEvent(new SpringApplication(), NO_ARGS));
multicastEvent(listener, new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS));
listener.initialize(this.context.getEnvironment(), this.context.getClassLoader());
assertThat(listener.shutdownHook).isNotNull();
listener.shutdownHook.start();
@ -426,7 +430,7 @@ class LoggingApplicationListenerTests {
@Test
void closingContextCleansUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName());
multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0]));
multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0]));
TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse();
@ -437,7 +441,7 @@ class LoggingApplicationListenerTests {
@Test
void closingChildContextDoesNotCleanUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName());
multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0]));
multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0]));
TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse();
@ -496,7 +500,7 @@ class LoggingApplicationListenerTests {
@Test
void applicationFailedEventCleansUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName());
multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0]));
multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0]));
TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse();

@ -1,199 +0,0 @@
/*
* 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.env;
import java.util.concurrent.atomic.AtomicInteger;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AssertProvider;
import org.junit.jupiter.api.Test;
import org.springframework.boot.env.BootstrapRegistry.Registration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link DefaultBootstrapRegisty}.
*
* @author Phillip Webb
*/
class DefaultBootstrapRegistyTests {
private DefaultBootstrapRegisty registy = new DefaultBootstrapRegisty();
private AtomicInteger counter = new AtomicInteger();
private StaticApplicationContext context = new StaticApplicationContext();
@Test
void getWhenNotRegisteredCreateInstance() {
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement);
assertThat(result).isEqualTo(0);
}
@Test
void getWhenAlreadyRegisteredReturnsExisting() {
this.registy.get(Integer.class, this.counter::getAndIncrement);
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement);
assertThat(result).isEqualTo(0);
}
@Test
void getWithPreparedActionRegistersAction() {
TestApplicationPreparedAction action = new TestApplicationPreparedAction();
Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement, action::run);
this.registy.applicationContextPrepared(this.context);
assertThat(result).isEqualTo(0);
assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0);
}
@Test
void getWithPreparedActionWhenAlreadyRegisteredIgnoresRegistersAction() {
TestApplicationPreparedAction action1 = new TestApplicationPreparedAction();
TestApplicationPreparedAction action2 = new TestApplicationPreparedAction();
this.registy.get(Integer.class, this.counter::getAndIncrement, action1::run);
this.registy.get(Integer.class, this.counter::getAndIncrement, action2::run);
this.registy.applicationContextPrepared(this.context);
assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0);
assertThat(action2).wasNotCalled();
}
@Test
void registerAddsRegistration() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registerWhenAlreadyRegisteredReplacesPreviousRegistration() {
Registration<Integer> registration1 = this.registy.register(Integer.class, this.counter::getAndIncrement);
Registration<Integer> registration2 = this.registy.register(Integer.class, () -> -1);
assertThat(registration2).isNotEqualTo(registration1);
assertThat(registration1.get()).isEqualTo(0);
assertThat(registration2.get()).isEqualTo(-1);
assertThat(this.registy.get(Integer.class, this.counter::getAndIncrement)).isEqualTo(-1);
}
@Test
void isRegisteredWhenNotRegisteredReturnsFalse() {
assertThat(this.registy.isRegistered(Integer.class)).isFalse();
}
@Test
void isRegisteredWhenRegisteredReturnsTrue() {
this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(this.registy.isRegistered(Integer.class)).isTrue();
}
@Test
void getRegistrationWhenNotRegisteredReturnsNull() {
assertThat(this.registy.getRegistration(Integer.class)).isNull();
}
@Test
void getRegistrationWhenRegisteredReturnsRegistration() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(this.registy.getRegistration(Integer.class)).isSameAs(registration);
}
@Test
void applicationContextPreparedTriggersActions() {
TestApplicationPreparedAction action1 = new TestApplicationPreparedAction();
TestApplicationPreparedAction action2 = new TestApplicationPreparedAction();
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(action1::run);
registration.onApplicationContextPrepared(action2::run);
this.registy.applicationContextPrepared(this.context);
assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0);
assertThat(action2).wasCalledOnlyOnce().hasInstanceValue(0);
}
@Test
void registrationGetReturnsInstance() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registrationGetWhenCalledMultipleTimesReturnsSingleInstance() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
assertThat(registration.get()).isEqualTo(0);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registrationOnApplicationContextPreparedAddsAction() {
TestApplicationPreparedAction action = new TestApplicationPreparedAction();
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(action::run);
this.registy.applicationContextPrepared(this.context);
assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0);
}
@Test
void registrationOnApplicationContextPreparedWhenActionIsNullDoesNotAddAction() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(null);
this.registy.applicationContextPrepared(this.context);
}
private static class TestApplicationPreparedAction implements AssertProvider<ApplicationPreparedActionAssert> {
private Integer instance;
private int called;
void run(ConfigurableApplicationContext context, Integer instance) {
this.instance = instance;
this.called++;
}
@Override
public ApplicationPreparedActionAssert assertThat() {
return new ApplicationPreparedActionAssert(this);
}
}
private static class ApplicationPreparedActionAssert
extends AbstractAssert<ApplicationPreparedActionAssert, TestApplicationPreparedAction> {
ApplicationPreparedActionAssert(TestApplicationPreparedAction actual) {
super(actual, ApplicationPreparedActionAssert.class);
}
ApplicationPreparedActionAssert hasInstanceValue(Integer expected) {
assertThat(this.actual.instance).isEqualTo(expected);
return this;
}
ApplicationPreparedActionAssert wasCalledOnlyOnce() {
assertThat(this.actual.called).as("action calls").isEqualTo(1);
return this;
}
ApplicationPreparedActionAssert wasNotCalled() {
assertThat(this.actual.called).as("action calls").isEqualTo(0);
return this;
}
}
}

@ -20,6 +20,8 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
@ -45,23 +47,22 @@ class EnvironmentPostProcessorApplicationListenerTests {
private DeferredLogs deferredLogs = spy(new DeferredLogs());
private DefaultBootstrapRegisty bootstrapRegistry = spy(new DefaultBootstrapRegisty());
private DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext());
private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs,
this.bootstrapRegistry);
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs);
@Test
void createUsesSpringFactories() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
assertThat(listener.getEnvironmentPostProcessors()).hasSizeGreaterThan(1);
assertThat(listener.getEnvironmentPostProcessors(this.bootstrapContext)).hasSizeGreaterThan(1);
}
@Test
void createWhenHasFactoryUsesFactory() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors();
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(this.bootstrapContext);
assertThat(postProcessors).hasSize(1);
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}
@ -90,8 +91,8 @@ class EnvironmentPostProcessorApplicationListenerTests {
void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() {
SpringApplication application = mock(SpringApplication.class);
MockEnvironment environment = new MockEnvironment();
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(application, new String[0],
environment);
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(this.bootstrapContext,
application, new String[0], environment);
this.listener.onApplicationEvent(event);
assertThat(environment.getProperty("processed")).isEqualTo("true");
}
@ -105,15 +106,6 @@ class EnvironmentPostProcessorApplicationListenerTests {
verify(this.deferredLogs).switchOverAll();
}
@Test
void onApplicationEventWhenApplicationPreparedEventTriggersRegistryActions() {
SpringApplication application = mock(SpringApplication.class);
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context);
this.listener.onApplicationEvent(event);
verify(this.bootstrapRegistry).applicationContextPrepared(context);
}
@Test
void onApplicationEventWhenApplicationFailedEventSwitchesLogs() {
SpringApplication application = mock(SpringApplication.class);

@ -21,6 +21,7 @@ import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
@ -36,13 +37,13 @@ class EnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Test
void fromSpringFactoriesReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry);
this.bootstrapContext);
assertThat(processors).hasSizeGreaterThan(1);
}
@ -51,7 +52,7 @@ class EnvironmentPostProcessorsFactoryTests {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry);
this.bootstrapContext);
assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}
@ -61,7 +62,7 @@ class EnvironmentPostProcessorsFactoryTests {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class.getName());
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry);
this.bootstrapContext);
assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}

@ -24,6 +24,8 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
@ -40,7 +42,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Test
void createWithClassesCreatesFactory() {
@ -96,7 +98,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
BadEnvironmentPostProcessor.class.getName());
assertThatIllegalArgumentException()
.isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapRegistry))
.isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapContext))
.withMessageContaining("Unable to instantiate");
}
@ -115,7 +117,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
void createsSinglePostProcessor(Class<?> expectedType) {
List<EnvironmentPostProcessor> processors = this.factory.getEnvironmentPostProcessors(
ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory,
ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapRegistry);
ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapContext);
assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(expectedType);
}

@ -0,0 +1,12 @@
plugins {
id "java"
id "org.springframework.boot.conventions"
}
description = "Spring Boot Bootstrap Registry smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
}

@ -0,0 +1,33 @@
/*
* 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 smoketest.bootstrapregistry.app;
import smoketest.bootstrapregistry.external.svn.SubversionClient;
import smoketest.bootstrapregistry.external.svn.SubversionServerCertificate;
public class MySubversionClient extends SubversionClient {
public MySubversionClient(SubversionServerCertificate serverCertificate) {
super(serverCertificate);
}
@Override
public String load(String location) {
return "my-" + super.load(location);
}
}

@ -0,0 +1,32 @@
/*
* 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 smoketest.bootstrapregistry.app;
import smoketest.bootstrapregistry.external.svn.SubversionClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Printer {
Printer(@Value("${svn}") String svn, SubversionClient subversionClient) {
System.out.println("--- svn " + svn);
System.out.println("--- client " + subversionClient.getClass().getName());
}
}

@ -0,0 +1,36 @@
/*
* 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 smoketest.bootstrapregistry.app;
import smoketest.bootstrapregistry.external.svn.SubversionBootstrap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleBootstrapRegistryApplication {
public static void main(String[] args) {
// This example shows how a Bootstrapper can be used to register a custom
// SubversionClient that still has access to data provided in the
// application.properties file
SpringApplication application = new SpringApplication(SampleBootstrapRegistryApplication.class);
application.addBootstrapper(SubversionBootstrap.withCustomClient(MySubversionClient::new));
application.run(args);
}
}

@ -0,0 +1,50 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
import java.util.function.Function;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.Bootstrapper;
/**
* Allows the user to register a {@link Bootstrapper} with a custom
* {@link SubversionClient}.
*
* @author Phillip Webb
*/
public final class SubversionBootstrap {
private SubversionBootstrap() {
}
/**
* Return a {@link Bootstrapper} for the given client factory.
* @param clientFactory the client factory
* @return a {@link Bootstrapper} instance
*/
public static Bootstrapper withCustomClient(Function<SubversionServerCertificate, SubversionClient> clientFactory) {
return (registry) -> registry.register(SubversionClient.class,
(bootstrapContext) -> createSubversionClient(bootstrapContext, clientFactory));
}
private static SubversionClient createSubversionClient(BootstrapContext bootstrapContext,
Function<SubversionServerCertificate, SubversionClient> clientFactory) {
return clientFactory.apply(bootstrapContext.get(SubversionServerCertificate.class));
}
}

@ -0,0 +1,36 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
/**
* A client that can connect to a subversion server.
*
* @author Phillip Webb
*/
public class SubversionClient {
private SubversionServerCertificate serverCertificate;
public SubversionClient(SubversionServerCertificate serverCertificate) {
this.serverCertificate = serverCertificate;
}
public String load(String location) {
return "data from svn / " + location + "[" + this.serverCertificate + "]";
}
}

@ -0,0 +1,68 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
import java.io.IOException;
import java.util.Collections;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapContextClosedEvent;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataLoader;
import org.springframework.boot.context.config.ConfigDataLoaderContext;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
/**
* {@link ConfigDataLoader} for subversion.
*
* @author Phillip Webb
*/
class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDataLocation> {
private static final ApplicationListener<BootstrapContextClosedEvent> closeListener = SubversionConfigDataLoader::onBootstrapContextClosed;
SubversionConfigDataLoader(BootstrapRegistry bootstrapRegistry) {
bootstrapRegistry.registerIfAbsent(SubversionClient.class, this::createSubversionClient);
bootstrapRegistry.addCloseListener(closeListener);
}
private SubversionClient createSubversionClient(BootstrapContext bootstrapContext) {
return new SubversionClient(bootstrapContext.get(SubversionServerCertificate.class));
}
@Override
public ConfigData load(ConfigDataLoaderContext context, SubversionConfigDataLocation location)
throws IOException, ConfigDataLocationNotFoundException {
context.getBootstrapContext().registerIfAbsent(SubversionServerCertificate.class,
InstanceSupplier.of(location.getServerCertificate()));
SubversionClient client = context.getBootstrapContext().get(SubversionClient.class);
String loaded = client.load(location.getLocation());
PropertySource<?> propertySource = new MapPropertySource("svn", Collections.singletonMap("svn", loaded));
return new ConfigData(Collections.singleton(propertySource));
}
private static void onBootstrapContextClosed(BootstrapContextClosedEvent event) {
event.getApplicationContext().getBeanFactory().registerSingleton("subversionClient",
event.getBootstrapContext().get(SubversionClient.class));
}
}

@ -0,0 +1,67 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
import org.springframework.boot.context.config.ConfigDataLocation;
/**
* A subversion {@link ConfigDataLocation}.
*
* @author Phillip Webb
*/
class SubversionConfigDataLocation extends ConfigDataLocation {
private final String location;
private final SubversionServerCertificate serverCertificate;
SubversionConfigDataLocation(String location, String serverCertificate) {
this.location = location;
this.serverCertificate = SubversionServerCertificate.of(serverCertificate);
}
String getLocation() {
return this.location;
}
SubversionServerCertificate getServerCertificate() {
return this.serverCertificate;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SubversionConfigDataLocation other = (SubversionConfigDataLocation) obj;
return this.location.equals(other.location);
}
@Override
public int hashCode() {
return this.location.hashCode();
}
@Override
public String toString() {
return this.location;
}
}

@ -0,0 +1,48 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
/**
* {@link ConfigDataLocationResolver} for subversion.
*
* @author Phillip Webb
*/
class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver<SubversionConfigDataLocation> {
private static final String PREFIX = "svn:";
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
return location.startsWith(PREFIX);
}
@Override
public List<SubversionConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
boolean optional) throws ConfigDataLocationNotFoundException {
String serverCertificate = context.getBinder().bind("spring.svn.server.certificate", String.class).orElse(null);
return Collections.singletonList(
new SubversionConfigDataLocation(location.substring(PREFIX.length()), serverCertificate));
}
}

@ -0,0 +1,43 @@
/*
* 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 smoketest.bootstrapregistry.external.svn;
import org.springframework.util.StringUtils;
/**
* A certificate that can be used to provide a secure connection to the subversion server.
*
* @author Phillip Webb
*/
public class SubversionServerCertificate {
private final String data;
SubversionServerCertificate(String data) {
this.data = data;
}
@Override
public String toString() {
return this.data;
}
public static SubversionServerCertificate of(String data) {
return StringUtils.hasText(data) ? new SubversionServerCertificate(data) : null;
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* An example of a hypothetical library that supports subversion.
*/
package smoketest.bootstrapregistry.external.svn;

@ -0,0 +1,5 @@
org.springframework.boot.context.config.ConfigDataLocationResolver=\
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLocationResolver
org.springframework.boot.context.config.ConfigDataLoader=\
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLoader

@ -0,0 +1,42 @@
/*
* 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 smoketest.bootstrapregistry.app;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SampleBootstrapRegistryApplication}.
*
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class SampleBootstrapRegistryApplicationTests {
@Test
void testBootrapper(CapturedOutput output) {
SampleBootstrapRegistryApplication.main(new String[0]);
assertThat(output).contains("svn my-data from svn / example.com[secret]")
.contains("client smoketest.bootstrapregistry.app.MySubversionClient");
}
}
Loading…
Cancel
Save