Commit 1ae14362 authored by Phillip Webb's avatar Phillip Webb

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
parent 27095d90
...@@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.TempDir; ...@@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
...@@ -78,8 +79,8 @@ class LiquibaseAutoConfigurationTests { ...@@ -78,8 +79,8 @@ class LiquibaseAutoConfigurationTests {
@BeforeEach @BeforeEach
void init() { void init() {
new LiquibaseServiceLocatorApplicationListener() new LiquibaseServiceLocatorApplicationListener().onApplicationEvent(new ApplicationStartingEvent(
.onApplicationEvent(new ApplicationStartingEvent(new SpringApplication(Object.class), new String[0])); new DefaultBootstrapContext(), new SpringApplication(Object.class), new String[0]));
} }
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; ...@@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
...@@ -90,9 +91,10 @@ class RestartApplicationListenerTests { ...@@ -90,9 +91,10 @@ class RestartApplicationListenerTests {
private void testInitialize(boolean failed) { private void testInitialize(boolean failed) {
Restarter.clearInstance(); Restarter.clearInstance();
RestartApplicationListener listener = new RestartApplicationListener(); RestartApplicationListener listener = new RestartApplicationListener();
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
SpringApplication application = new SpringApplication(); SpringApplication application = new SpringApplication();
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); 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()).isNotEqualTo(nullValue());
assertThat(Restarter.getInstance().isFinished()).isFalse(); assertThat(Restarter.getInstance().isFinished()).isFalse();
listener.onApplicationEvent(new ApplicationPreparedEvent(application, ARGS, context)); listener.onApplicationEvent(new ApplicationPreparedEvent(application, ARGS, context));
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
package org.springframework.boot.test.context; package org.springframework.boot.test.context;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource; import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -42,9 +42,9 @@ public class ConfigDataApplicationContextInitializer ...@@ -42,9 +42,9 @@ public class ConfigDataApplicationContextInitializer
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment(); ConfigurableEnvironment environment = applicationContext.getEnvironment();
RandomValuePropertySource.addToEnvironment(environment); RandomValuePropertySource.addToEnvironment(environment);
DefaultBootstrapRegisty bootstrapRegistry = new DefaultBootstrapRegisty(); DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapRegistry); ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapContext);
bootstrapRegistry.applicationContextPrepared(applicationContext); bootstrapContext.close(applicationContext);
DefaultPropertiesPropertySource.moveToEnd(environment); DefaultPropertiesPropertySource.moveToEnd(environment);
} }
......
/*
* 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;
}
/*
* 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;
}
}
...@@ -14,73 +14,55 @@ ...@@ -14,73 +14,55 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.env; package org.springframework.boot;
import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import io.undertow.servlet.api.InstanceFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
/** /**
* A simple object registry that is available during {@link Environment} post-processing * A simple object registry that is available during startup and {@link Environment}
* up to the point that the {@link ApplicationContext} is prepared. The registry can be * post-processing up to the point that the {@link ApplicationContext} is prepared.
* used to store objects that may be expensive to create, or need to be shared by * <p>
* different {@link EnvironmentPostProcessor EnvironmentPostProcessors}. * Can be used to register instances that may be expensive to create, or need to be shared
* before the {@link ApplicationContext} is available.
* <p> * <p>
* The registry uses the object type as a key, meaning that only a single instance of a * The registry uses {@link Class} as a key, meaning that only a single instance of a
* given class can be stored. * given type can be stored.
* <p> * <p>
* Registered instances may optionally use * The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener
* {@link Registration#onApplicationContextPrepared(BiConsumer) * that can perform actions when {@link BootstrapContext} has been closed and the
* onApplicationContextPrepared(...)} to perform an action when the * {@link ApplicationContext} is fully prepared. For example, an instance may choose to
* {@link ApplicationContext} is {@link ApplicationPreparedEvent prepared}. For example, * register itself as a regular Spring bean so that it is available for the application to
* an instance may choose to register itself as a regular Spring bean so that it is * use.
* available for the application to use.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
* @see EnvironmentPostProcessor * @see BootstrapContext
* @see ConfigurableBootstrapContext
*/ */
public interface BootstrapRegistry { public interface BootstrapRegistry {
/** /**
* Get an instance from the registry, creating one if it does not already exist. * Register a specific type with the registry. If the specified type has already been
* @param <T> the instance type * registered, but not get obtained, it will be replaced.
* @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 <T> the instance type
* @param type the instance type * @param type the instance type
* @param instanceSupplier a supplier used to create the instance if it doesn't * @param instanceSupplier the instance supplier
* 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, <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction);
/** /**
* Register an instance with the registry and return a {@link Registration} that can * Register a specific type with the registry if one is not already present.
* be used to provide further configuration. This method will replace any existing
* registration.
* @param <T> the instance type * @param <T> the instance type
* @param type the instance type * @param type the instance type
* @param instanceSupplier a supplier used to create the instance if it doesn't * @param instanceSupplier the instance supplier
* already exist
* @return an instance registration
*/ */
<T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier); <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);
/** /**
* Return if a registration exists for the given type. * Return if a registration exists for the given type.
...@@ -91,32 +73,57 @@ public interface BootstrapRegistry { ...@@ -91,32 +73,57 @@ public interface BootstrapRegistry {
<T> boolean isRegistered(Class<T> type); <T> boolean isRegistered(Class<T> type);
/** /**
* Return any existing {@link Registration} for the given type. * Return any existing {@link InstanceFactory} for the given type.
* @param <T> the instance type * @param <T> the instance type
* @param type the instance type * @param type the instance type
* @return the existing registration or {@code null} * @return the registered {@link InstanceSupplier} or {@code null}
*/ */
<T> Registration<T> getRegistration(Class<T> type); <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);
/** /**
* A single registration contained in the registry. * 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 * @param <T> the instance type
*/ */
interface Registration<T> { 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);
/** /**
* Get or crearte the registered object instance. * Factory method that can be used to create a {@link InstanceFactory} for a given
* @return the object instance * instance.
* @param <T> the instance type
* @param instance the instance
* @return a new {@link InstanceFactory}
*/ */
T get(); static <T> InstanceSupplier<T> of(T instance) {
return (registry) -> instance;
}
/** /**
* Add an action that should run when the {@link ApplicationContext} has been * Factory method that can be used to create a {@link InstanceFactory} from a
* prepared. * {@link Supplier}.
* @param action the action to run * @param <T> the instance type
* @param supplier the supplier that will provide the instance
* @return a new {@link InstanceFactory}
*/ */
void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action); static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
return (registry) -> (supplier != null) ? supplier.get() : null;
}
} }
......
/*
* 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);
}
/*
* 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 {
}
/*
* 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 { ...@@ -236,6 +236,8 @@ public class SpringApplication {
private Map<String, Object> defaultProperties; private Map<String, Object> defaultProperties;
private List<Bootstrapper> bootstrappers;
private Set<String> additionalProfiles = Collections.emptySet(); private Set<String> additionalProfiles = Collections.emptySet();
private boolean allowBeanDefinitionOverriding; private boolean allowBeanDefinitionOverriding;
...@@ -278,6 +280,7 @@ public class SpringApplication { ...@@ -278,6 +280,7 @@ public class SpringApplication {
Assert.notNull(primarySources, "PrimarySources must not be null"); Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass(); this.mainApplicationClass = deduceMainApplicationClass();
...@@ -307,21 +310,22 @@ public class SpringApplication { ...@@ -307,21 +310,22 @@ public class SpringApplication {
public ConfigurableApplicationContext run(String... args) { public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); StopWatch stopWatch = new StopWatch();
stopWatch.start(); stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null; ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty(); configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args); SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(this.mainApplicationClass); listeners.starting(bootstrapContext, this.mainApplicationClass);
try { try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment); configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); Banner printedBanner = printBanner(environment);
context = createApplicationContext(); context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup); context.setApplicationStartup(this.applicationStartup);
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class<?>[] { ConfigurableApplicationContext.class }, context); new Class<?>[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner); prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context); refreshContext(context);
afterRefresh(context, applicationArguments); afterRefresh(context, applicationArguments);
stopWatch.stop(); stopWatch.stop();
...@@ -346,13 +350,19 @@ public class SpringApplication { ...@@ -346,13 +350,19 @@ public class SpringApplication {
return context; return context;
} }
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
return bootstrapContext;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) { DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment // Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment(); ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs()); configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment); ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment); listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment); DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment); configureAdditionalProfiles(environment);
bindToSpringApplication(environment); bindToSpringApplication(environment);
...@@ -375,12 +385,14 @@ public class SpringApplication { ...@@ -375,12 +385,14 @@ public class SpringApplication {
} }
} }
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); context.setEnvironment(environment);
postProcessApplicationContext(context); postProcessApplicationContext(context);
applyInitializers(context); applyInitializers(context);
listeners.contextPrepared(context); listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) { if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null); logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context); logStartupProfileInfo(context);
...@@ -1028,6 +1040,17 @@ public class SpringApplication { ...@@ -1028,6 +1040,17 @@ public class SpringApplication {
this.addConversionService = addConversionService; 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 * Set default environment properties which will be used in addition to those in the
* existing {@link Environment}. * existing {@link Environment}.
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -38,15 +38,40 @@ public interface SpringApplicationRunListener { ...@@ -38,15 +38,40 @@ public interface SpringApplicationRunListener {
/** /**
* Called immediately when the run method has first started. Can be used for very * Called immediately when the run method has first started. Can be used for very
* early initialization. * 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() { 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 * Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created. * {@link ApplicationContext} has been created.
* @param environment the environment * @param environment the environment
* @deprecated since 2.4.0 in favor of
* {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
*/ */
@Deprecated
default void environmentPrepared(ConfigurableEnvironment environment) { default void environmentPrepared(ConfigurableEnvironment environment) {
} }
......
...@@ -49,17 +49,18 @@ class SpringApplicationRunListeners { ...@@ -49,17 +49,18 @@ class SpringApplicationRunListeners {
this.applicationStartup = applicationStartup; this.applicationStartup = applicationStartup;
} }
void starting(Class<?> mainApplicationClass) { void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", SpringApplicationRunListener::starting, (step) -> { doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
if (mainApplicationClass != null) { (step) -> {
step.tag("mainApplicationClass", mainApplicationClass.getName()); if (mainApplicationClass != null) {
} step.tag("mainApplicationClass", mainApplicationClass.getName());
}); }
});
} }
void environmentPrepared(ConfigurableEnvironment environment) { void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared", doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(environment)); (listener) -> listener.environmentPrepared(bootstrapContext, environment));
} }
void contextPrepared(ConfigurableApplicationContext context) { void contextPrepared(ConfigurableApplicationContext context) {
......
...@@ -30,6 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -30,6 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.ApplicationContextFactory; import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.Banner; import org.springframework.boot.Banner;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.Bootstrapper;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.convert.ApplicationConversionService;
...@@ -397,6 +399,18 @@ public class SpringApplicationBuilder { ...@@ -397,6 +399,18 @@ public class SpringApplicationBuilder {
return this; 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. * Flag to control whether the application should be initialized lazily.
* @param lazyInitialization the flag to set. Defaults to false. * @param lazyInitialization the flag to set. Defaults to false.
......
...@@ -25,6 +25,7 @@ import java.util.Set; ...@@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource; import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption; import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption;
import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.BindException;
...@@ -33,7 +34,6 @@ import org.springframework.boot.context.properties.bind.Binder; ...@@ -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.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
...@@ -97,7 +97,7 @@ class ConfigDataEnvironment { ...@@ -97,7 +97,7 @@ class ConfigDataEnvironment {
private final Log logger; private final Log logger;
private final BootstrapRegistry bootstrapRegistry; private final ConfigurableBootstrapContext bootstrapContext;
private final ConfigurableEnvironment environment; private final ConfigurableEnvironment environment;
...@@ -112,12 +112,12 @@ class ConfigDataEnvironment { ...@@ -112,12 +112,12 @@ class ConfigDataEnvironment {
/** /**
* Create a new {@link ConfigDataEnvironment} instance. * Create a new {@link ConfigDataEnvironment} instance.
* @param logFactory the deferred log factory * @param logFactory the deferred log factory
* @param bootstrapRegistry the bootstrap registry * @param bootstrapContext the bootstrap context
* @param environment the Spring {@link Environment}. * @param environment the Spring {@link Environment}.
* @param resourceLoader {@link ResourceLoader} to load resource locations * @param resourceLoader {@link ResourceLoader} to load resource locations
* @param additionalProfiles any additional profiles to activate * @param additionalProfiles any additional profiles to activate
*/ */
ConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) { ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
Binder binder = Binder.get(environment); Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder); UseLegacyConfigProcessingException.throwIfRequested(binder);
...@@ -126,17 +126,20 @@ class ConfigDataEnvironment { ...@@ -126,17 +126,20 @@ class ConfigDataEnvironment {
.orElse(ConfigDataLocationNotFoundAction.FAIL); .orElse(ConfigDataLocationNotFoundAction.FAIL);
this.logFactory = logFactory; this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry; this.bootstrapContext = bootstrapContext;
this.environment = environment; this.environment = environment;
this.resolvers = createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader); this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
this.additionalProfiles = additionalProfiles; this.additionalProfiles = additionalProfiles;
this.loaders = new ConfigDataLoaders(logFactory, locationNotFoundAction); this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, locationNotFoundAction);
this.contributors = createContributors(binder); this.contributors = createContributors(binder);
} }
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction,
return new ConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader); Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
} }
private ConfigDataEnvironmentContributors createContributors(Binder binder) { private ConfigDataEnvironmentContributors createContributors(Binder binder) {
...@@ -159,7 +162,7 @@ class ConfigDataEnvironment { ...@@ -159,7 +162,7 @@ class ConfigDataEnvironment {
this.logger.trace("Creating wrapped config data contributor for default property source"); this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource)); contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
} }
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, contributors); return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
} }
ConfigDataEnvironmentContributors getContributors() { ConfigDataEnvironmentContributors getContributors() {
......
...@@ -28,6 +28,7 @@ import java.util.stream.Stream; ...@@ -28,6 +28,7 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log; 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.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindContext;
...@@ -37,7 +38,6 @@ import org.springframework.boot.context.properties.bind.Binder; ...@@ -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.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.Origin;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
...@@ -56,25 +56,25 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -56,25 +56,25 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
private final ConfigDataEnvironmentContributor root; private final ConfigDataEnvironmentContributor root;
private final BootstrapRegistry bootstrapRegistry; private final ConfigurableBootstrapContext bootstrapContext;
/** /**
* Create a new {@link ConfigDataEnvironmentContributors} instance. * Create a new {@link ConfigDataEnvironmentContributors} instance.
* @param logFactory the log factory * @param logFactory the log factory
* @param bootstrapRegistry the bootstrap registry * @param bootstrapContext the bootstrap context
* @param contributors the initial set of contributors * @param contributors the initial set of contributors
*/ */
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<ConfigDataEnvironmentContributor> contributors) { List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry; this.bootstrapContext = bootstrapContext;
this.root = ConfigDataEnvironmentContributor.of(contributors); this.root = ConfigDataEnvironmentContributor.of(contributors);
} }
private ConfigDataEnvironmentContributors(Log logger, BootstrapRegistry bootstrapRegistry, private ConfigDataEnvironmentContributors(Log logger, ConfigurableBootstrapContext bootstrapContext,
ConfigDataEnvironmentContributor root) { ConfigDataEnvironmentContributor root) {
this.logger = logger; this.logger = logger;
this.bootstrapRegistry = bootstrapRegistry; this.bootstrapContext = bootstrapContext;
this.root = root; this.root = root;
} }
...@@ -107,7 +107,7 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -107,7 +107,7 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
result, activationContext, true); result, activationContext, true);
Binder binder = new Binder(sources, placeholdersResolver, null, null, null); Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder); ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry, result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound)); result.getRoot().withReplacement(contributor, bound));
continue; continue;
} }
...@@ -122,14 +122,14 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -122,14 +122,14 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
+ imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet())); + imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet()));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported)); asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry, result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren)); result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++; processed++;
} }
} }
protected final BootstrapRegistry getBootstrapRegistry() { protected final ConfigurableBootstrapContext getBootstrapContext() {
return this.bootstrapRegistry; return this.bootstrapContext;
} }
private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors, private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors,
...@@ -222,8 +222,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -222,8 +222,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
} }
@Override @Override
public BootstrapRegistry getBootstrapRegistry() { public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapRegistry(); return this.contributors.getBootstrapContext();
} }
} }
...@@ -264,8 +264,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -264,8 +264,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
} }
@Override @Override
public BootstrapRegistry getBootstrapRegistry() { public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapRegistry(); return this.contributors.getBootstrapContext();
} }
@Override @Override
......
...@@ -23,9 +23,9 @@ import java.util.function.Supplier; ...@@ -23,9 +23,9 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log; 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.SpringApplication;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
...@@ -61,12 +61,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces ...@@ -61,12 +61,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
private final Log logger; 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.logFactory = logFactory;
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry; this.bootstrapContext = bootstrapContext;
} }
@Override @Override
...@@ -95,7 +96,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces ...@@ -95,7 +96,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) { Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, environment, resourceLoader, return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles); additionalProfiles);
} }
...@@ -125,13 +126,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces ...@@ -125,13 +126,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
* directly and not necessarily as part of a {@link SpringApplication}. * directly and not necessarily as part of a {@link SpringApplication}.
* @param environment the environment to apply {@link ConfigData} to * @param environment the environment to apply {@link ConfigData} to
* @param resourceLoader the resource loader to use * @param resourceLoader the resource loader to use
* @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a * @param bootstrapContext the bootstrap context to use or {@code null} to use a
* throw-away registry * throw-away context
* @param additionalProfiles any additional profiles that should be applied * @param additionalProfiles any additional profiles that should be applied
*/ */
public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
BootstrapRegistry bootstrapRegistry, String... additionalProfiles) { ConfigurableBootstrapContext bootstrapContext, String... additionalProfiles) {
applyTo(environment, resourceLoader, bootstrapRegistry, Arrays.asList(additionalProfiles)); applyTo(environment, resourceLoader, bootstrapContext, Arrays.asList(additionalProfiles));
} }
/** /**
...@@ -140,16 +141,16 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces ...@@ -140,16 +141,16 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
* directly and not necessarily as part of a {@link SpringApplication}. * directly and not necessarily as part of a {@link SpringApplication}.
* @param environment the environment to apply {@link ConfigData} to * @param environment the environment to apply {@link ConfigData} to
* @param resourceLoader the resource loader to use * @param resourceLoader the resource loader to use
* @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a * @param bootstrapContext the bootstrap context to use or {@code null} to use a
* throw-away registry * throw-away context
* @param additionalProfiles any additional profiles that should be applied * @param additionalProfiles any additional profiles that should be applied
*/ */
public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
BootstrapRegistry bootstrapRegistry, Collection<String> additionalProfiles) { ConfigurableBootstrapContext bootstrapContext, Collection<String> additionalProfiles) {
DeferredLogFactory logFactory = Supplier::get; DeferredLogFactory logFactory = Supplier::get;
bootstrapRegistry = (bootstrapRegistry != null) ? bootstrapRegistry : new DefaultBootstrapRegisty(); bootstrapContext = (bootstrapContext != null) ? bootstrapContext : new DefaultBootstrapContext();
ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory, ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory,
bootstrapRegistry); bootstrapContext);
postProcessor.postProcessEnvironment(environment, resourceLoader, additionalProfiles); postProcessor.postProcessEnvironment(environment, resourceLoader, additionalProfiles);
} }
......
...@@ -20,6 +20,10 @@ import java.io.IOException; ...@@ -20,6 +20,10 @@ import java.io.IOException;
import org.apache.commons.logging.Log; 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 * Strategy class that can be used used to load {@link ConfigData} instances from a
* {@link ConfigDataLocation location}. Implementations should be added as a * {@link ConfigDataLocation location}. Implementations should be added as a
...@@ -27,6 +31,9 @@ import org.apache.commons.logging.Log; ...@@ -27,6 +31,9 @@ import org.apache.commons.logging.Log;
* supported: * supported:
* <ul> * <ul>
* <li>{@link Log} - if the resolver needs deferred logging</li> * <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> * </ul>
* <p> * <p>
* Multiple loaders cannot claim the same location. * Multiple loaders cannot claim the same location.
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
/** /**
...@@ -28,10 +28,10 @@ import org.springframework.boot.env.EnvironmentPostProcessor; ...@@ -28,10 +28,10 @@ import org.springframework.boot.env.EnvironmentPostProcessor;
public interface ConfigDataLoaderContext { public interface ConfigDataLoaderContext {
/** /**
* Provides access to the {@link BootstrapRegistry} shared across all * Provides access to the {@link ConfigurableBootstrapContext} shared across all
* {@link EnvironmentPostProcessor EnvironmentPostProcessors}. * {@link EnvironmentPostProcessor EnvironmentPostProcessors}.
* @return the bootstrap registry * @return the bootstrap context
*/ */
BootstrapRegistry getBootstrapRegistry(); ConfigurableBootstrapContext getBootstrapContext();
} }
...@@ -23,6 +23,9 @@ import java.util.List; ...@@ -23,6 +23,9 @@ import java.util.List;
import org.apache.commons.logging.Log; 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.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator; import org.springframework.boot.util.Instantiator;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
...@@ -48,27 +51,36 @@ class ConfigDataLoaders { ...@@ -48,27 +51,36 @@ class ConfigDataLoaders {
/** /**
* Create a new {@link ConfigDataLoaders} instance. * 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 * @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown * {@link ConfigDataLocationNotFoundException} is thrown
* @param logFactory the deferred log factory
*/ */
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction) { ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
this(logFactory, locationNotFoundAction, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null)); ConfigDataLocationNotFoundAction locationNotFoundAction) {
this(logFactory, bootstrapContext, locationNotFoundAction,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
} }
/** /**
* Create a new {@link ConfigDataLoaders} instance. * Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory * @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a * @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown * {@link ConfigDataLocationNotFoundException} is thrown
* @param names the {@link ConfigDataLoader} class names instantiate * @param names the {@link ConfigDataLoader} class names instantiate
*/ */
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<String> names) { ConfigDataLocationNotFoundAction locationNotFoundAction, List<String> names) {
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction; this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class, 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.loaders = instantiator.instantiate(names);
this.locationTypes = getLocationTypes(this.loaders); this.locationTypes = getLocationTypes(this.loaders);
} }
......
...@@ -21,6 +21,9 @@ import java.util.List; ...@@ -21,6 +21,9 @@ import java.util.List;
import org.apache.commons.logging.Log; 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.context.properties.bind.Binder;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
...@@ -36,6 +39,9 @@ import org.springframework.core.io.ResourceLoader; ...@@ -36,6 +39,9 @@ import org.springframework.core.io.ResourceLoader;
* <li>{@link Binder} - if the resolver needs to obtain values from the initial * <li>{@link Binder} - if the resolver needs to obtain values from the initial
* {@link Environment}</li> * {@link Environment}</li>
* <li>{@link ResourceLoader} - if the resolver needs a resource loader</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> * </ul>
* <p> * <p>
* Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The * Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.Origin;
...@@ -45,11 +45,11 @@ public interface ConfigDataLocationResolverContext { ...@@ -45,11 +45,11 @@ public interface ConfigDataLocationResolverContext {
ConfigDataLocation getParent(); ConfigDataLocation getParent();
/** /**
* Provides access to the {@link BootstrapRegistry} shared across all * Provides access to the {@link ConfigurableBootstrapContext} shared across all
* {@link EnvironmentPostProcessor EnvironmentPostProcessors}. * {@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. * Return the {@link Origin} of a location that's being resolved.
......
...@@ -23,6 +23,9 @@ import java.util.function.Supplier; ...@@ -23,6 +23,9 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log; 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.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator; import org.springframework.boot.util.Instantiator;
...@@ -50,28 +53,31 @@ class ConfigDataLocationResolvers { ...@@ -50,28 +53,31 @@ class ConfigDataLocationResolvers {
/** /**
* Create a new {@link ConfigDataLocationResolvers} instance. * Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a * @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown * {@link ConfigDataLocationNotFoundException} is thrown
* @param binder a binder providing values from the initial {@link Environment} * @param binder a binder providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations * @param resourceLoader {@link ResourceLoader} to load resource locations
*/ */
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader) { ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) {
this(logFactory, locationNotFoundAction, binder, resourceLoader, this(logFactory, bootstrapContext, locationNotFoundAction, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null)); SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null));
} }
/** /**
* Create a new {@link ConfigDataLocationResolvers} instance. * Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a * @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown * {@link ConfigDataLocationNotFoundException} is thrown
* @param binder {@link Binder} providing values from the initial {@link Environment} * @param binder {@link Binder} providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations * @param resourceLoader {@link ResourceLoader} to load resource locations
* @param names the {@link ConfigDataLocationResolver} class names * @param names the {@link ConfigDataLocationResolver} class names
*/ */
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader, List<String> names) { ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader,
List<String> names) {
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction; this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class, Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
...@@ -79,6 +85,9 @@ class ConfigDataLocationResolvers { ...@@ -79,6 +85,9 @@ class ConfigDataLocationResolvers {
availableParameters.add(Log.class, logFactory::getLog); availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(Binder.class, binder); availableParameters.add(Binder.class, binder);
availableParameters.add(ResourceLoader.class, resourceLoader); 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)); this.resolvers = reorder(instantiator.instantiate(names));
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.context.event; package org.springframework.boot.context.event;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
...@@ -30,6 +31,8 @@ import org.springframework.core.env.Environment; ...@@ -30,6 +31,8 @@ import org.springframework.core.env.Environment;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent { public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent {
private final ConfigurableBootstrapContext bootstrapContext;
private final ConfigurableEnvironment environment; private final ConfigurableEnvironment environment;
/** /**
...@@ -37,13 +40,38 @@ public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent ...@@ -37,13 +40,38 @@ public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent
* @param application the current application * @param application the current application
* @param args the arguments the application is running with * @param args the arguments the application is running with
* @param environment the environment that was just created * @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, public ApplicationEnvironmentPreparedEvent(SpringApplication application, String[] args,
ConfigurableEnvironment environment) { 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); super(application, args);
this.bootstrapContext = bootstrapContext;
this.environment = environment; 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.
* @return the environment * @return the environment
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.context.event; package org.springframework.boot.context.event;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
...@@ -35,13 +36,39 @@ import org.springframework.core.env.Environment; ...@@ -35,13 +36,39 @@ import org.springframework.core.env.Environment;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ApplicationStartingEvent extends SpringApplicationEvent { public class ApplicationStartingEvent extends SpringApplicationEvent {
private final ConfigurableBootstrapContext bootstrapContext;
/** /**
* Create a new {@link ApplicationStartingEvent} instance. * Create a new {@link ApplicationStartingEvent} instance.
* @param application the current application * @param application the current application
* @param args the arguments the application is running with * @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) { 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); 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; ...@@ -19,6 +19,7 @@ package org.springframework.boot.context.event;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener; import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.AvailabilityChangeEvent;
...@@ -70,14 +71,16 @@ public class EventPublishingRunListener implements SpringApplicationRunListener, ...@@ -70,14 +71,16 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
} }
@Override @Override
public void starting() { public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
} }
@Override @Override
public void environmentPrepared(ConfigurableEnvironment environment) { public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
this.initialMulticaster ConfigurableEnvironment environment) {
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
} }
@Override @Override
......
/*
* 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; ...@@ -18,6 +18,9 @@ package org.springframework.boot.env;
import org.apache.commons.logging.Log; 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.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -41,8 +44,9 @@ import org.springframework.core.env.Environment; ...@@ -41,8 +44,9 @@ import org.springframework.core.env.Environment;
* itself to configure logging levels).</li> * itself to configure logging levels).</li>
* <li>{@link Log} - A log with output deferred until the application has been full * <li>{@link Log} - A log with output deferred until the application has been full
* prepared (allowing the environment itself to configure logging levels).</li> * prepared (allowing the environment itself to configure logging levels).</li>
* <li>{@link BootstrapRegistry} - A bootstrap registry that can be used to store objects * <li>{@link ConfigurableBootstrapContext} - A bootstrap context that can be used to
* that may be expensive to create, or need to be shared.</li> * store objects that may be expensive to create, or need to be shared
* ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li>
* </ul> * </ul>
* *
* @author Andy Wilkinson * @author Andy Wilkinson
......
...@@ -18,6 +18,7 @@ package org.springframework.boot.env; ...@@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.util.List; import java.util.List;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent;
...@@ -44,8 +45,6 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica ...@@ -44,8 +45,6 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private final DeferredLogs deferredLogs; private final DeferredLogs deferredLogs;
private final DefaultBootstrapRegisty bootstrapRegistry;
private int order = DEFAULT_ORDER; private int order = DEFAULT_ORDER;
private final EnvironmentPostProcessorsFactory postProcessorsFactory; private final EnvironmentPostProcessorsFactory postProcessorsFactory;
...@@ -65,14 +64,13 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica ...@@ -65,14 +64,13 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
* @param postProcessorsFactory the post processors factory * @param postProcessorsFactory the post processors factory
*/ */
public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) { public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) {
this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty()); this(postProcessorsFactory, new DeferredLogs());
} }
EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory, EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory,
DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) { DeferredLogs deferredLogs) {
this.postProcessorsFactory = postProcessorsFactory; this.postProcessorsFactory = postProcessorsFactory;
this.deferredLogs = deferredLogs; this.deferredLogs = deferredLogs;
this.bootstrapRegistry = bootstrapRegistry;
} }
@Override @Override
...@@ -98,13 +96,12 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica ...@@ -98,13 +96,12 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment(); ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication(); SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors()) { for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application); postProcessor.postProcessEnvironment(environment, application);
} }
} }
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext());
finish(); finish();
} }
...@@ -113,12 +110,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica ...@@ -113,12 +110,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
} }
private void finish() { private void finish() {
this.bootstrapRegistry.clear();
this.deferredLogs.switchOverAll(); this.deferredLogs.switchOverAll();
} }
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() { List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ConfigurableBootstrapContext bootstrapContext) {
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry); return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
} }
@Override @Override
......
...@@ -18,6 +18,7 @@ package org.springframework.boot.env; ...@@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.util.List; import java.util.List;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
...@@ -34,11 +35,11 @@ public interface EnvironmentPostProcessorsFactory { ...@@ -34,11 +35,11 @@ public interface EnvironmentPostProcessorsFactory {
/** /**
* Create all requested {@link EnvironmentPostProcessor} instances. * Create all requested {@link EnvironmentPostProcessor} instances.
* @param logFactory a deferred log factory * @param logFactory a deferred log factory
* @param bootstrapRegistry a bootstrap registry * @param bootstrapContext a bootstrap context
* @return the post processor instances * @return the post processor instances
*/ */
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory, List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
BootstrapRegistry bootstrapRegistry); ConfigurableBootstrapContext bootstrapContext);
/** /**
* Return a {@link EnvironmentPostProcessorsFactory} backed by * Return a {@link EnvironmentPostProcessorsFactory} backed by
......
...@@ -21,6 +21,9 @@ import java.util.List; ...@@ -21,6 +21,9 @@ import java.util.List;
import org.apache.commons.logging.Log; 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.logging.DeferredLogFactory;
import org.springframework.boot.util.Instantiator; import org.springframework.boot.util.Instantiator;
...@@ -48,12 +51,14 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce ...@@ -48,12 +51,14 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce
@Override @Override
public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory, public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
BootstrapRegistry bootstrapRegistry) { ConfigurableBootstrapContext bootstrapContext) {
Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class, Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,
(parameters) -> { (parameters) -> {
parameters.add(DeferredLogFactory.class, logFactory); parameters.add(DeferredLogFactory.class, logFactory);
parameters.add(Log.class, logFactory::getLog); 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); return instantiator.instantiate(this.classNames);
} }
......
/*
* 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 ...@@ -48,6 +48,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator; import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.AvailabilityState; import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.LivenessState; import org.springframework.boot.availability.LivenessState;
...@@ -105,6 +106,8 @@ import org.springframework.core.metrics.ApplicationStartup; ...@@ -105,6 +106,8 @@ import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep; import org.springframework.core.metrics.StartupStep;
import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.context.support.TestPropertySourceUtils; 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.util.StringUtils;
import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
...@@ -1201,6 +1204,35 @@ class SpringApplicationTests { ...@@ -1201,6 +1204,35 @@ class SpringApplicationTests {
assertThat(startCount).isEqualTo(endCount); 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( private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(
S state) { S state) {
return (argument) -> (argument instanceof AvailabilityChangeEvent<?>) return (argument) -> (argument instanceof AvailabilityChangeEvent<?>)
...@@ -1658,4 +1690,20 @@ class SpringApplicationTests { ...@@ -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 { ...@@ -282,6 +282,15 @@ class SpringApplicationBuilderTests {
this.context.getBean(ChildConfig.class); 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) @Configuration(proxyBeanMethods = false)
static class ExampleConfig { static class ExampleConfig {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach; ...@@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
...@@ -123,7 +124,8 @@ class ApplicationPidFileWriterTests { ...@@ -123,7 +124,8 @@ class ApplicationPidFileWriterTests {
File file = new File(this.tempDir, "pid"); File file = new File(this.tempDir, "pid");
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
listener.setTriggerEventType(ApplicationStartingEvent.class); 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(); assertThat(contentOf(file)).isNotEmpty();
} }
...@@ -170,7 +172,8 @@ class ApplicationPidFileWriterTests { ...@@ -170,7 +172,8 @@ class ApplicationPidFileWriterTests {
private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) { private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) {
ConfigurableEnvironment environment = createEnvironment(propName, 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) { private SpringApplicationEvent createPreparedEvent(String propName, String propValue) {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,6 +18,7 @@ package org.springframework.boot.context; ...@@ -18,6 +18,7 @@ package org.springframework.boot.context;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
...@@ -40,7 +41,7 @@ class FileEncodingApplicationListenerTests { ...@@ -40,7 +41,7 @@ class FileEncodingApplicationListenerTests {
private final ConfigurableEnvironment environment = new StandardEnvironment(); private final ConfigurableEnvironment environment = new StandardEnvironment();
private final ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent( private final ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(
new SpringApplication(), new String[0], this.environment); new DefaultBootstrapContext(), new SpringApplication(), new String[0], this.environment);
@Test @Test
void testIllegalState() { void testIllegalState() {
......
...@@ -19,10 +19,10 @@ package org.springframework.boot.context.config; ...@@ -19,10 +19,10 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.TestConfigDataBootstrap.LoaderHelper; import org.springframework.boot.context.config.TestConfigDataBootstrap.LoaderHelper;
import org.springframework.boot.env.BootstrapRegistry;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
......
...@@ -27,8 +27,8 @@ import org.mockito.Mock; ...@@ -27,8 +27,8 @@ import org.mockito.Mock;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.DefaultBootstrapRegisty;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
...@@ -60,7 +60,7 @@ class ConfigDataEnvironmentPostProcessorTests { ...@@ -60,7 +60,7 @@ class ConfigDataEnvironmentPostProcessorTests {
@Spy @Spy
private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get, private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get,
new DefaultBootstrapRegisty()); new DefaultBootstrapContext());
@Captor @Captor
private ArgumentCaptor<Set<String>> additionalProfilesCaptor; private ArgumentCaptor<Set<String>> additionalProfilesCaptor;
......
...@@ -24,6 +24,11 @@ import java.util.function.Supplier; ...@@ -24,6 +24,11 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test; 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.boot.logging.DeferredLogFactory;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.env.MockPropertySource;
...@@ -42,19 +47,28 @@ class ConfigDataLoadersTests { ...@@ -42,19 +47,28 @@ class ConfigDataLoadersTests {
private DeferredLogFactory logFactory = Supplier::get; private DeferredLogFactory logFactory = Supplier::get;
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class); private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class);
@Test @Test
void createWhenLoaderHasLogParameterInjectsLog() { void createWhenLoaderHasLogParameterInjectsLog() {
new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName())); 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 @Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception { void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
Arrays.asList(TestConfigDataLoader.class.getName())); ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location); ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class); assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
} }
...@@ -62,7 +76,8 @@ class ConfigDataLoadersTests { ...@@ -62,7 +76,8 @@ class ConfigDataLoadersTests {
@Test @Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception { void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); 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())); Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for location test"); .withMessageContaining("Multiple loaders found for location test");
...@@ -71,8 +86,8 @@ class ConfigDataLoadersTests { ...@@ -71,8 +86,8 @@ class ConfigDataLoadersTests {
@Test @Test
void loadWhenNoLoaderSupportsLocationThrowsException() { void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
Arrays.asList(NonLoadableConfigDataLoader.class.getName())); ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for location 'test'"); .withMessage("No loader found for location 'test'");
} }
...@@ -80,7 +95,8 @@ class ConfigDataLoadersTests { ...@@ -80,7 +95,8 @@ class ConfigDataLoadersTests {
@Test @Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception { void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); 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())); Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location); ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class); assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
...@@ -131,6 +147,24 @@ class ConfigDataLoadersTests { ...@@ -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> { static class TestConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> {
@Override @Override
......
...@@ -28,6 +28,11 @@ import org.junit.jupiter.api.extension.ExtendWith; ...@@ -28,6 +28,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; 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.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
...@@ -50,6 +55,8 @@ class ConfigDataLocationResolversTests { ...@@ -50,6 +55,8 @@ class ConfigDataLocationResolversTests {
private DeferredLogFactory logFactory = Supplier::get; private DeferredLogFactory logFactory = Supplier::get;
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Mock @Mock
private Binder binder; private Binder binder;
...@@ -63,7 +70,7 @@ class ConfigDataLocationResolversTests { ...@@ -63,7 +70,7 @@ class ConfigDataLocationResolversTests {
@Test @Test
void createWhenInjectingBinderCreatesResolver() { void createWhenInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestBoundResolver.class.getName())); Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers()).hasSize(1);
...@@ -73,17 +80,24 @@ class ConfigDataLocationResolversTests { ...@@ -73,17 +80,24 @@ class ConfigDataLocationResolversTests {
@Test @Test
void createWhenNotInjectingBinderCreatesResolver() { void createWhenNotInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(TestResolver.class.getName())); Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class); 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 @Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() { void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Collections.singletonList(InputStream.class.getName()))) Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable"); .withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
...@@ -95,7 +109,7 @@ class ConfigDataLocationResolversTests { ...@@ -95,7 +109,7 @@ class ConfigDataLocationResolversTests {
names.add(TestResolver.class.getName()); names.add(TestResolver.class.getName());
names.add(LowestTestResolver.class.getName()); names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.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); ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class); assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
...@@ -104,7 +118,7 @@ class ConfigDataLocationResolversTests { ...@@ -104,7 +118,7 @@ class ConfigDataLocationResolversTests {
@Test @Test
void resolveAllResolvesUsingFirstSupportedResolver() { void resolveAllResolvesUsingFirstSupportedResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context, List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
...@@ -118,7 +132,7 @@ class ConfigDataLocationResolversTests { ...@@ -118,7 +132,7 @@ class ConfigDataLocationResolversTests {
@Test @Test
void resolveAllWhenProfileMergesResolvedLocations() { void resolveAllWhenProfileMergesResolvedLocations() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context, List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
...@@ -136,7 +150,7 @@ class ConfigDataLocationResolversTests { ...@@ -136,7 +150,7 @@ class ConfigDataLocationResolversTests {
@Test @Test
void resolveWhenNoResolverThrowsException() { void resolveWhenNoResolverThrowsException() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class) assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
...@@ -181,6 +195,19 @@ class ConfigDataLocationResolversTests { ...@@ -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) @Order(Ordered.HIGHEST_PRECEDENCE)
static class HighestTestResolver extends TestResolver { static class HighestTestResolver extends TestResolver {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,6 +19,7 @@ package org.springframework.boot.context.config; ...@@ -19,6 +19,7 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
...@@ -53,8 +54,8 @@ class DelegatingApplicationListenerTests { ...@@ -53,8 +54,8 @@ class DelegatingApplicationListenerTests {
void orderedInitialize() { void orderedInitialize() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"context.listener.classes=" + MockInitB.class.getName() + "," + MockInitA.class.getName()); "context.listener.classes=" + MockInitB.class.getName() + "," + MockInitA.class.getName());
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
this.context.getEnvironment())); new SpringApplication(), new String[0], this.context.getEnvironment()));
this.context.getBeanFactory().registerSingleton("testListener", this.listener); this.context.getBeanFactory().registerSingleton("testListener", this.listener);
this.context.refresh(); this.context.refresh();
assertThat(this.context.getBeanFactory().getSingleton("a")).isEqualTo("a"); assertThat(this.context.getBeanFactory().getSingleton("a")).isEqualTo("a");
...@@ -63,15 +64,15 @@ class DelegatingApplicationListenerTests { ...@@ -63,15 +64,15 @@ class DelegatingApplicationListenerTests {
@Test @Test
void noInitializers() { void noInitializers() {
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
this.context.getEnvironment())); new SpringApplication(), new String[0], this.context.getEnvironment()));
} }
@Test @Test
void emptyInitializers() { void emptyInitializers() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "context.listener.classes:"); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "context.listener.classes:");
this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(),
this.context.getEnvironment())); new SpringApplication(), new String[0], this.context.getEnvironment()));
} }
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
......
...@@ -20,7 +20,9 @@ import java.io.IOException; ...@@ -20,7 +20,9 @@ import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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; import org.springframework.core.env.MapPropertySource;
/** /**
...@@ -42,8 +44,9 @@ class TestConfigDataBootstrap { ...@@ -42,8 +44,9 @@ class TestConfigDataBootstrap {
@Override @Override
public List<Location> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) { public List<Location> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) {
ResolverHelper helper = context.getBootstrapRegistry().get(ResolverHelper.class, context.getBootstrapContext().registerIfAbsent(ResolverHelper.class,
() -> new ResolverHelper(location)); InstanceSupplier.from(() -> new ResolverHelper(location)));
ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class);
return Collections.singletonList(new Location(helper)); return Collections.singletonList(new Location(helper));
} }
...@@ -53,8 +56,10 @@ class TestConfigDataBootstrap { ...@@ -53,8 +56,10 @@ class TestConfigDataBootstrap {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException {
context.getBootstrapRegistry().get(LoaderHelper.class, () -> new LoaderHelper(location), context.getBootstrapContext().registerIfAbsent(LoaderHelper.class,
LoaderHelper::addToContext); InstanceSupplier.from(() -> new LoaderHelper(location)));
LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class);
context.getBootstrapContext().addCloseListener(helper);
return new ConfigData( return new ConfigData(
Collections.singleton(new MapPropertySource("loaded", Collections.singletonMap("test", "test")))); Collections.singleton(new MapPropertySource("loaded", Collections.singletonMap("test", "test"))));
} }
...@@ -94,7 +99,7 @@ class TestConfigDataBootstrap { ...@@ -94,7 +99,7 @@ class TestConfigDataBootstrap {
} }
static class LoaderHelper { static class LoaderHelper implements ApplicationListener<BootstrapContextClosedEvent> {
private final Location location; private final Location location;
...@@ -106,8 +111,9 @@ class TestConfigDataBootstrap { ...@@ -106,8 +111,9 @@ class TestConfigDataBootstrap {
return this.location; return this.location;
} }
static void addToContext(ConfigurableApplicationContext context, LoaderHelper loaderHelper) { @Override
context.getBeanFactory().registerSingleton("loaderHelper", loaderHelper); public void onApplicationEvent(BootstrapContextClosedEvent event) {
event.getApplicationContext().getBeanFactory().registerSingleton("loaderHelper", this);
} }
} }
......
...@@ -25,6 +25,7 @@ import java.util.List; ...@@ -25,6 +25,7 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
...@@ -42,6 +43,8 @@ import static org.mockito.Mockito.mock; ...@@ -42,6 +43,8 @@ import static org.mockito.Mockito.mock;
*/ */
class EventPublishingRunListenerTests { class EventPublishingRunListenerTests {
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
private SpringApplication application; private SpringApplication application;
private EventPublishingRunListener runListener; private EventPublishingRunListener runListener;
...@@ -60,9 +63,9 @@ class EventPublishingRunListenerTests { ...@@ -60,9 +63,9 @@ class EventPublishingRunListenerTests {
void shouldPublishLifecycleEvents() { void shouldPublishLifecycleEvents() {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
assertThat(this.eventListener.receivedEvents()).isEmpty(); assertThat(this.eventListener.receivedEvents()).isEmpty();
this.runListener.starting(); this.runListener.starting(this.bootstrapContext);
checkApplicationEvents(ApplicationStartingEvent.class); checkApplicationEvents(ApplicationStartingEvent.class);
this.runListener.environmentPrepared(null); this.runListener.environmentPrepared(this.bootstrapContext, null);
checkApplicationEvents(ApplicationEnvironmentPreparedEvent.class); checkApplicationEvents(ApplicationEnvironmentPreparedEvent.class);
this.runListener.contextPrepared(context); this.runListener.contextPrepared(context);
checkApplicationEvents(ApplicationContextInitializedEvent.class); checkApplicationEvents(ApplicationContextInitializedEvent.class);
......
...@@ -41,6 +41,7 @@ import org.junit.jupiter.api.io.TempDir; ...@@ -41,6 +41,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.bridge.SLF4JBridgeHandler;
import org.slf4j.impl.StaticLoggerBinder; import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.boot.context.event.ApplicationStartingEvent;
...@@ -97,6 +98,8 @@ class LoggingApplicationListenerTests { ...@@ -97,6 +98,8 @@ class LoggingApplicationListenerTests {
private final ch.qos.logback.classic.Logger logger = this.loggerContext.getLogger(getClass()); 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 SpringApplication springApplication = new SpringApplication();
private final GenericApplicationContext context = new GenericApplicationContext(); private final GenericApplicationContext context = new GenericApplicationContext();
...@@ -113,7 +116,7 @@ class LoggingApplicationListenerTests { ...@@ -113,7 +116,7 @@ class LoggingApplicationListenerTests {
this.output = output; this.output = output;
this.logFile = new File(this.tempDir.toFile(), "foo.log"); this.logFile = new File(this.tempDir.toFile(), "foo.log");
LogManager.getLogManager().readConfiguration(JavaLoggingSystem.class.getResourceAsStream("logging.properties")); 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(); new File(this.tempDir.toFile(), "spring.log").delete();
ConfigurableEnvironment environment = this.context.getEnvironment(); ConfigurableEnvironment environment = this.context.getEnvironment();
ConfigurationPropertySources.attach(environment); ConfigurationPropertySources.attach(environment);
...@@ -372,7 +375,8 @@ class LoggingApplicationListenerTests { ...@@ -372,7 +375,8 @@ class LoggingApplicationListenerTests {
void parseArgsDoesntReplace() { void parseArgsDoesntReplace() {
this.initializer.setSpringBootLogging(LogLevel.ERROR); this.initializer.setSpringBootLogging(LogLevel.ERROR);
this.initializer.setParseArgs(false); 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.initializer.initialize(this.context.getEnvironment(), this.context.getClassLoader());
this.logger.debug("testatdebug"); this.logger.debug("testatdebug");
assertThat(this.output).doesNotContain("testatdebug"); assertThat(this.output).doesNotContain("testatdebug");
...@@ -406,7 +410,7 @@ class LoggingApplicationListenerTests { ...@@ -406,7 +410,7 @@ class LoggingApplicationListenerTests {
void shutdownHookIsNotRegisteredByDefault() { void shutdownHookIsNotRegisteredByDefault() {
TestLoggingApplicationListener listener = new TestLoggingApplicationListener(); TestLoggingApplicationListener listener = new TestLoggingApplicationListener();
System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName()); 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()); listener.initialize(this.context.getEnvironment(), this.context.getClassLoader());
assertThat(listener.shutdownHook).isNull(); assertThat(listener.shutdownHook).isNull();
} }
...@@ -416,7 +420,7 @@ class LoggingApplicationListenerTests { ...@@ -416,7 +420,7 @@ class LoggingApplicationListenerTests {
TestLoggingApplicationListener listener = new TestLoggingApplicationListener(); TestLoggingApplicationListener listener = new TestLoggingApplicationListener();
System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName()); System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName());
addPropertiesToEnvironment(this.context, "logging.register_shutdown_hook=true"); 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()); listener.initialize(this.context.getEnvironment(), this.context.getClassLoader());
assertThat(listener.shutdownHook).isNotNull(); assertThat(listener.shutdownHook).isNotNull();
listener.shutdownHook.start(); listener.shutdownHook.start();
...@@ -426,7 +430,7 @@ class LoggingApplicationListenerTests { ...@@ -426,7 +430,7 @@ class LoggingApplicationListenerTests {
@Test @Test
void closingContextCleansUpLoggingSystem() { void closingContextCleansUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); 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 TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem"); .getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse(); assertThat(loggingSystem.cleanedUp).isFalse();
...@@ -437,7 +441,7 @@ class LoggingApplicationListenerTests { ...@@ -437,7 +441,7 @@ class LoggingApplicationListenerTests {
@Test @Test
void closingChildContextDoesNotCleanUpLoggingSystem() { void closingChildContextDoesNotCleanUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); 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 TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem"); .getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse(); assertThat(loggingSystem.cleanedUp).isFalse();
...@@ -496,7 +500,7 @@ class LoggingApplicationListenerTests { ...@@ -496,7 +500,7 @@ class LoggingApplicationListenerTests {
@Test @Test
void applicationFailedEventCleansUpLoggingSystem() { void applicationFailedEventCleansUpLoggingSystem() {
System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); 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 TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils
.getField(this.initializer, "loggingSystem"); .getField(this.initializer, "loggingSystem");
assertThat(loggingSystem.cleanedUp).isFalse(); assertThat(loggingSystem.cleanedUp).isFalse();
......
/*
* 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; ...@@ -20,6 +20,8 @@ import java.util.List;
import org.junit.jupiter.api.Test; 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.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent;
...@@ -45,23 +47,22 @@ class EnvironmentPostProcessorApplicationListenerTests { ...@@ -45,23 +47,22 @@ class EnvironmentPostProcessorApplicationListenerTests {
private DeferredLogs deferredLogs = spy(new DeferredLogs()); private DeferredLogs deferredLogs = spy(new DeferredLogs());
private DefaultBootstrapRegisty bootstrapRegistry = spy(new DefaultBootstrapRegisty()); private DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext());
private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs, EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs);
this.bootstrapRegistry);
@Test @Test
void createUsesSpringFactories() { void createUsesSpringFactories() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener();
assertThat(listener.getEnvironmentPostProcessors()).hasSizeGreaterThan(1); assertThat(listener.getEnvironmentPostProcessors(this.bootstrapContext)).hasSizeGreaterThan(1);
} }
@Test @Test
void createWhenHasFactoryUsesFactory() { void createWhenHasFactoryUsesFactory() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(); List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(this.bootstrapContext);
assertThat(postProcessors).hasSize(1); assertThat(postProcessors).hasSize(1);
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
} }
...@@ -90,8 +91,8 @@ class EnvironmentPostProcessorApplicationListenerTests { ...@@ -90,8 +91,8 @@ class EnvironmentPostProcessorApplicationListenerTests {
void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() { void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() {
SpringApplication application = mock(SpringApplication.class); SpringApplication application = mock(SpringApplication.class);
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(application, new String[0], ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(this.bootstrapContext,
environment); application, new String[0], environment);
this.listener.onApplicationEvent(event); this.listener.onApplicationEvent(event);
assertThat(environment.getProperty("processed")).isEqualTo("true"); assertThat(environment.getProperty("processed")).isEqualTo("true");
} }
...@@ -105,15 +106,6 @@ class EnvironmentPostProcessorApplicationListenerTests { ...@@ -105,15 +106,6 @@ class EnvironmentPostProcessorApplicationListenerTests {
verify(this.deferredLogs).switchOverAll(); 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 @Test
void onApplicationEventWhenApplicationFailedEventSwitchesLogs() { void onApplicationEventWhenApplicationFailedEventSwitchesLogs() {
SpringApplication application = mock(SpringApplication.class); SpringApplication application = mock(SpringApplication.class);
......
...@@ -21,6 +21,7 @@ import java.util.function.Supplier; ...@@ -21,6 +21,7 @@ import java.util.function.Supplier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -36,13 +37,13 @@ class EnvironmentPostProcessorsFactoryTests { ...@@ -36,13 +37,13 @@ class EnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get; private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Test @Test
void fromSpringFactoriesReturnsFactory() { void fromSpringFactoriesReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null); EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory, List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry); this.bootstrapContext);
assertThat(processors).hasSizeGreaterThan(1); assertThat(processors).hasSizeGreaterThan(1);
} }
...@@ -51,7 +52,7 @@ class EnvironmentPostProcessorsFactoryTests { ...@@ -51,7 +52,7 @@ class EnvironmentPostProcessorsFactoryTests {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class); .of(TestEnvironmentPostProcessor.class);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory, List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry); this.bootstrapContext);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
} }
...@@ -61,7 +62,7 @@ class EnvironmentPostProcessorsFactoryTests { ...@@ -61,7 +62,7 @@ class EnvironmentPostProcessorsFactoryTests {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class.getName()); .of(TestEnvironmentPostProcessor.class.getName());
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory, List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry); this.bootstrapContext);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
} }
......
...@@ -24,6 +24,8 @@ import java.util.function.Supplier; ...@@ -24,6 +24,8 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test; 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.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -40,7 +42,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { ...@@ -40,7 +42,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get; private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
@Test @Test
void createWithClassesCreatesFactory() { void createWithClassesCreatesFactory() {
...@@ -96,7 +98,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { ...@@ -96,7 +98,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
BadEnvironmentPostProcessor.class.getName()); BadEnvironmentPostProcessor.class.getName());
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapRegistry)) .isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapContext))
.withMessageContaining("Unable to instantiate"); .withMessageContaining("Unable to instantiate");
} }
...@@ -115,7 +117,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { ...@@ -115,7 +117,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
void createsSinglePostProcessor(Class<?> expectedType) { void createsSinglePostProcessor(Class<?> expectedType) {
List<EnvironmentPostProcessor> processors = this.factory.getEnvironmentPostProcessors( List<EnvironmentPostProcessor> processors = this.factory.getEnvironmentPostProcessors(
ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory, ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory,
ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapRegistry); ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapContext);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(expectedType); assertThat(processors.get(0)).isInstanceOf(expectedType);
} }
......
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"))
}
/*
* 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);
}
}
/*
* 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());
}
}
/*
* 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);
}
}
/*
* 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));
}
}
/*
* 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 + "]";
}
}
/*
* 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));
}
}
/*
* 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;
}
}
/*
* 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));
}
}
/*
* 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;
}
}
/*
* 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;
org.springframework.boot.context.config.ConfigDataLocationResolver=\
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLocationResolver
org.springframework.boot.context.config.ConfigDataLoader=\
smoketest.bootstrapregistry.external.svn.SubversionConfigDataLoader
/*
* 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");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment