diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index 7f854d2c529..b4596651e95 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; @@ -78,8 +79,8 @@ class LiquibaseAutoConfigurationTests { @BeforeEach void init() { - new LiquibaseServiceLocatorApplicationListener() - .onApplicationEvent(new ApplicationStartingEvent(new SpringApplication(Object.class), new String[0])); + new LiquibaseServiceLocatorApplicationListener().onApplicationEvent(new ApplicationStartingEvent( + new DefaultBootstrapContext(), new SpringApplication(Object.class), new String[0])); } private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java index 05bf343d31d..923781c9a69 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; @@ -90,9 +91,10 @@ class RestartApplicationListenerTests { private void testInitialize(boolean failed) { Restarter.clearInstance(); RestartApplicationListener listener = new RestartApplicationListener(); + DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); SpringApplication application = new SpringApplication(); ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - listener.onApplicationEvent(new ApplicationStartingEvent(application, ARGS)); + listener.onApplicationEvent(new ApplicationStartingEvent(bootstrapContext, application, ARGS)); assertThat(Restarter.getInstance()).isNotEqualTo(nullValue()); assertThat(Restarter.getInstance().isFinished()).isFalse(); listener.onApplicationEvent(new ApplicationPreparedEvent(application, ARGS, context)); diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java index 935cd6f1cab..a7b0adbe4b7 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java @@ -16,10 +16,10 @@ package org.springframework.boot.test.context; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.DefaultPropertiesPropertySource; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; -import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -42,9 +42,9 @@ public class ConfigDataApplicationContextInitializer public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); RandomValuePropertySource.addToEnvironment(environment); - DefaultBootstrapRegisty bootstrapRegistry = new DefaultBootstrapRegisty(); - ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapRegistry); - bootstrapRegistry.applicationContextPrepared(applicationContext); + DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapContext); + bootstrapContext.close(applicationContext); DefaultPropertiesPropertySource.moveToEnd(environment); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java new file mode 100644 index 00000000000..4155ec535a7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +/** + * A simple bootstrap context that is available during startup and {@link Environment} + * post-processing up to the point that the {@link ApplicationContext} is prepared. + *

+ * 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 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 get(Class type) throws IllegalStateException; + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java new file mode 100644 index 00000000000..2be023e4731 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * {@link ApplicationEvent} published by a {@link BootstrapContext} when it's closed. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapRegistry#addCloseListener(org.springframework.context.ApplicationListener) + */ +public class BootstrapContextClosedEvent extends ApplicationEvent { + + private final ConfigurableApplicationContext applicationContext; + + BootstrapContextClosedEvent(BootstrapContext source, ConfigurableApplicationContext applicationContext) { + super(source); + this.applicationContext = applicationContext; + } + + /** + * Return the {@link BootstrapContext} that was closed. + * @return the bootstrap context + */ + public BootstrapContext getBootstrapContext() { + return (BootstrapContext) this.source; + } + + /** + * Return the prepared application context. + * @return the application context + */ + public ConfigurableApplicationContext getApplicationContext() { + return this.applicationContext; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java new file mode 100644 index 00000000000..940986800d8 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import java.util.function.Supplier; + +import io.undertow.servlet.api.InstanceFactory; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; + +/** + * A simple object registry that is available during startup and {@link Environment} + * post-processing up to the point that the {@link ApplicationContext} is prepared. + *

+ * Can be used to register instances that may be expensive to create, or need to be shared + * before the {@link ApplicationContext} is available. + *

+ * The registry uses {@link Class} as a key, meaning that only a single instance of a + * given type can be stored. + *

+ * The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener + * that can perform actions when {@link BootstrapContext} has been closed and the + * {@link ApplicationContext} is fully prepared. For example, an instance may choose to + * register itself as a regular Spring bean so that it is available for the application to + * use. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapContext + * @see ConfigurableBootstrapContext + */ +public interface BootstrapRegistry { + + /** + * Register a specific type with the registry. If the specified type has already been + * registered, but not get obtained, it will be replaced. + * @param the instance type + * @param type the instance type + * @param instanceSupplier the instance supplier + */ + void register(Class type, InstanceSupplier instanceSupplier); + + /** + * Register a specific type with the registry if one is not already present. + * @param the instance type + * @param type the instance type + * @param instanceSupplier the instance supplier + */ + void registerIfAbsent(Class type, InstanceSupplier instanceSupplier); + + /** + * Return if a registration exists for the given type. + * @param the instance type + * @param type the instance type + * @return {@code true} if the type has already been registered + */ + boolean isRegistered(Class type); + + /** + * Return any existing {@link InstanceFactory} for the given type. + * @param the instance type + * @param type the instance type + * @return the registered {@link InstanceSupplier} or {@code null} + */ + InstanceSupplier getRegisteredInstanceSupplier(Class type); + + /** + * Add an {@link ApplicationListener} that will be called with a + * {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and + * the {@link ApplicationContext} has been prepared. + * @param listener the listener to add + */ + void addCloseListener(ApplicationListener listener); + + /** + * Supplier used to provide the actual instance the first time it is accessed. + * + * @param the instance type + */ + public interface InstanceSupplier { + + /** + * Factory method used to create the instance when needed. + * @param context the {@link BootstrapContext} which may be used to obtain other + * bootstrap instances. + * @return the instance + */ + T get(BootstrapContext context); + + /** + * Factory method that can be used to create a {@link InstanceFactory} for a given + * instance. + * @param the instance type + * @param instance the instance + * @return a new {@link InstanceFactory} + */ + static InstanceSupplier of(T instance) { + return (registry) -> instance; + } + + /** + * Factory method that can be used to create a {@link InstanceFactory} from a + * {@link Supplier}. + * @param the instance type + * @param supplier the supplier that will provide the instance + * @return a new {@link InstanceFactory} + */ + static InstanceSupplier from(Supplier supplier) { + return (registry) -> (supplier != null) ? supplier.get() : null; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/Bootstrapper.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/Bootstrapper.java new file mode 100644 index 00000000000..4e67042bece --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/Bootstrapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +/** + * Callback interface that can be used to initialize a {@link BootstrapRegistry} before it + * is used. + * + * @author Phillip Webb + * @since 2.4.0 + * @see SpringApplication#addBootstrapper(Bootstrapper) + * @see BootstrapRegistry + */ +public interface Bootstrapper { + + /** + * Initialize the given {@link BootstrapRegistry} with any required registrations. + * @param registry the registry to initialize + */ + void intitialize(BootstrapRegistry registry); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java new file mode 100644 index 00000000000..432c1a70784 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +/** + * A {@link BootstrapContext} that also provides configuration methods via the + * {@link BootstrapRegistry} interface. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapRegistry + * @see BootstrapContext + * @see DefaultBootstrapContext + */ +public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java new file mode 100644 index 00000000000..9806d34916a --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.util.Assert; + +/** + * Default {@link ConfigurableBootstrapContext} implementation. + * + * @author Phillip Webb + * @since 2.4.0 + */ +public class DefaultBootstrapContext implements ConfigurableBootstrapContext { + + private final Map, InstanceSupplier> instanceSuppliers = new HashMap<>(); + + private final Map, Object> instances = new HashMap<>(); + + private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster(); + + @Override + public void register(Class type, InstanceSupplier instanceSupplier) { + register(type, instanceSupplier, true); + } + + @Override + public void registerIfAbsent(Class type, InstanceSupplier instanceSupplier) { + register(type, instanceSupplier, false); + } + + private void register(Class type, InstanceSupplier 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 boolean isRegistered(Class type) { + synchronized (this.instanceSuppliers) { + return this.instanceSuppliers.containsKey(type); + } + } + + @Override + @SuppressWarnings("unchecked") + public InstanceSupplier getRegisteredInstanceSupplier(Class type) { + synchronized (this.instanceSuppliers) { + return (InstanceSupplier) this.instanceSuppliers.get(type); + } + } + + @Override + public void addCloseListener(ApplicationListener listener) { + this.events.addApplicationListener(listener); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Class 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)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index f9462f36eac..13f22f0cc9b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -236,6 +236,8 @@ public class SpringApplication { private Map defaultProperties; + private List bootstrappers; + private Set additionalProfiles = Collections.emptySet(); private boolean allowBeanDefinitionOverriding; @@ -278,6 +280,7 @@ public class SpringApplication { Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); + this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); @@ -307,21 +310,22 @@ public class SpringApplication { public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); + DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); - listeners.starting(this.mainApplicationClass); + listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); - ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); + ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); - prepareContext(context, environment, listeners, applicationArguments, printedBanner); + prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); @@ -346,13 +350,19 @@ public class SpringApplication { return context; } + private DefaultBootstrapContext createBootstrapContext() { + DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext)); + return bootstrapContext; + } + private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, - ApplicationArguments applicationArguments) { + DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); - listeners.environmentPrepared(environment); + listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); configureAdditionalProfiles(environment); bindToSpringApplication(environment); @@ -375,12 +385,14 @@ public class SpringApplication { } } - private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, - SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { + private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, + ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, + ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); + bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); @@ -1028,6 +1040,17 @@ public class SpringApplication { this.addConversionService = addConversionService; } + /** + * Adds a {@link Bootstrapper} that can be used to initialize the + * {@link BootstrapRegistry}. + * @param bootstrapper the bootstraper + * @since 2.4.0 + */ + public void addBootstrapper(Bootstrapper bootstrapper) { + Assert.notNull(bootstrapper, "Bootstrapper must not be null"); + this.bootstrappers.add(bootstrapper); + } + /** * Set default environment properties which will be used in addition to those in the * existing {@link Environment}. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java index 325febe884a..5b4fefb3b57 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,15 +38,40 @@ public interface SpringApplicationRunListener { /** * Called immediately when the run method has first started. Can be used for very * early initialization. + * @param bootstrapContext the bootstrap context */ + default void starting(ConfigurableBootstrapContext bootstrapContext) { + starting(); + } + + /** + * Called immediately when the run method has first started. Can be used for very + * early initialization. + * @deprecated since 2.4.0 in favor of {@link #starting(ConfigurableBootstrapContext)} + */ + @Deprecated default void starting() { } /** * Called once the environment has been prepared, but before the * {@link ApplicationContext} has been created. + * @param bootstrapContext the bootstrap context * @param environment the environment */ + default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, + ConfigurableEnvironment environment) { + environmentPrepared(environment); + } + + /** + * Called once the environment has been prepared, but before the + * {@link ApplicationContext} has been created. + * @param environment the environment + * @deprecated since 2.4.0 in favor of + * {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)} + */ + @Deprecated default void environmentPrepared(ConfigurableEnvironment environment) { } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java index 4acb1a92706..21c26ef8fa5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java @@ -49,17 +49,18 @@ class SpringApplicationRunListeners { this.applicationStartup = applicationStartup; } - void starting(Class mainApplicationClass) { - doWithListeners("spring.boot.application.starting", SpringApplicationRunListener::starting, (step) -> { - if (mainApplicationClass != null) { - step.tag("mainApplicationClass", mainApplicationClass.getName()); - } - }); + void starting(ConfigurableBootstrapContext bootstrapContext, Class mainApplicationClass) { + doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext), + (step) -> { + if (mainApplicationClass != null) { + step.tag("mainApplicationClass", mainApplicationClass.getName()); + } + }); } - void environmentPrepared(ConfigurableEnvironment environment) { + void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { doWithListeners("spring.boot.application.environment-prepared", - (listener) -> listener.environmentPrepared(environment)); + (listener) -> listener.environmentPrepared(bootstrapContext, environment)); } void contextPrepared(ConfigurableApplicationContext context) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java index 8b7045e13a2..289d8219ca6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java @@ -30,6 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.ApplicationContextFactory; import org.springframework.boot.Banner; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.Bootstrapper; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.convert.ApplicationConversionService; @@ -397,6 +399,18 @@ public class SpringApplicationBuilder { return this; } + /** + * Adds a {@link Bootstrapper} that can be used to initialize the + * {@link BootstrapRegistry}. + * @param bootstrapper the bootstraper + * @return the current builder + * @since 2.4.0 + */ + public SpringApplicationBuilder addBootstrapper(Bootstrapper bootstrapper) { + this.application.addBootstrapper(bootstrapper); + return this; + } + /** * Flag to control whether the application should be initialized lazily. * @param lazyInitialization the flag to set. Defaults to false. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 2daf112d9f4..d3a0eba3547 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.commons.logging.Log; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.DefaultPropertiesPropertySource; import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption; import org.springframework.boot.context.properties.bind.BindException; @@ -33,7 +34,6 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; -import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -97,7 +97,7 @@ class ConfigDataEnvironment { private final Log logger; - private final BootstrapRegistry bootstrapRegistry; + private final ConfigurableBootstrapContext bootstrapContext; private final ConfigurableEnvironment environment; @@ -112,12 +112,12 @@ class ConfigDataEnvironment { /** * Create a new {@link ConfigDataEnvironment} instance. * @param logFactory the deferred log factory - * @param bootstrapRegistry the bootstrap registry + * @param bootstrapContext the bootstrap context * @param environment the Spring {@link Environment}. * @param resourceLoader {@link ResourceLoader} to load resource locations * @param additionalProfiles any additional profiles to activate */ - ConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, + ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles) { Binder binder = Binder.get(environment); UseLegacyConfigProcessingException.throwIfRequested(binder); @@ -126,17 +126,20 @@ class ConfigDataEnvironment { .orElse(ConfigDataLocationNotFoundAction.FAIL); this.logFactory = logFactory; this.logger = logFactory.getLog(getClass()); - this.bootstrapRegistry = bootstrapRegistry; + this.bootstrapContext = bootstrapContext; this.environment = environment; - this.resolvers = createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader); + this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, + resourceLoader); this.additionalProfiles = additionalProfiles; - this.loaders = new ConfigDataLoaders(logFactory, locationNotFoundAction); + this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, locationNotFoundAction); this.contributors = createContributors(binder); } protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, - ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { - return new ConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader); + ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, + Binder binder, ResourceLoader resourceLoader) { + return new ConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, + resourceLoader); } private ConfigDataEnvironmentContributors createContributors(Binder binder) { @@ -159,7 +162,7 @@ class ConfigDataEnvironment { this.logger.trace("Creating wrapped config data contributor for default property source"); contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource)); } - return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, contributors); + return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors); } ConfigDataEnvironmentContributors getContributors() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java index 6526e5a8666..053c2d06612 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java @@ -28,6 +28,7 @@ import java.util.stream.Stream; import org.apache.commons.logging.Log; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.properties.bind.BindContext; @@ -37,7 +38,6 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; -import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.origin.Origin; import org.springframework.core.log.LogMessage; @@ -56,25 +56,25 @@ class ConfigDataEnvironmentContributors implements Iterable contributors) { this.logger = logFactory.getLog(getClass()); - this.bootstrapRegistry = bootstrapRegistry; + this.bootstrapContext = bootstrapContext; this.root = ConfigDataEnvironmentContributor.of(contributors); } - private ConfigDataEnvironmentContributors(Log logger, BootstrapRegistry bootstrapRegistry, + private ConfigDataEnvironmentContributors(Log logger, ConfigurableBootstrapContext bootstrapContext, ConfigDataEnvironmentContributor root) { this.logger = logger; - this.bootstrapRegistry = bootstrapRegistry; + this.bootstrapContext = bootstrapContext; this.root = root; } @@ -107,7 +107,7 @@ class ConfigDataEnvironmentContributors implements Iterable additionalProfiles) { - return new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, environment, resourceLoader, + return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles); } @@ -125,13 +126,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * directly and not necessarily as part of a {@link SpringApplication}. * @param environment the environment to apply {@link ConfigData} to * @param resourceLoader the resource loader to use - * @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a - * throw-away registry + * @param bootstrapContext the bootstrap context to use or {@code null} to use a + * throw-away context * @param additionalProfiles any additional profiles that should be applied */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - BootstrapRegistry bootstrapRegistry, String... additionalProfiles) { - applyTo(environment, resourceLoader, bootstrapRegistry, Arrays.asList(additionalProfiles)); + ConfigurableBootstrapContext bootstrapContext, String... additionalProfiles) { + applyTo(environment, resourceLoader, bootstrapContext, Arrays.asList(additionalProfiles)); } /** @@ -140,16 +141,16 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * directly and not necessarily as part of a {@link SpringApplication}. * @param environment the environment to apply {@link ConfigData} to * @param resourceLoader the resource loader to use - * @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a - * throw-away registry + * @param bootstrapContext the bootstrap context to use or {@code null} to use a + * throw-away context * @param additionalProfiles any additional profiles that should be applied */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - BootstrapRegistry bootstrapRegistry, Collection additionalProfiles) { + ConfigurableBootstrapContext bootstrapContext, Collection additionalProfiles) { DeferredLogFactory logFactory = Supplier::get; - bootstrapRegistry = (bootstrapRegistry != null) ? bootstrapRegistry : new DefaultBootstrapRegisty(); + bootstrapContext = (bootstrapContext != null) ? bootstrapContext : new DefaultBootstrapContext(); ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory, - bootstrapRegistry); + bootstrapContext); postProcessor.postProcessEnvironment(environment, resourceLoader, additionalProfiles); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java index 921fdb8057f..8c3fa0f6b17 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java @@ -20,6 +20,10 @@ import java.io.IOException; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; + /** * Strategy class that can be used used to load {@link ConfigData} instances from a * {@link ConfigDataLocation location}. Implementations should be added as a @@ -27,6 +31,9 @@ import org.apache.commons.logging.Log; * supported: *

*

* Multiple loaders cannot claim the same location. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java index e46211a6f32..50e0bf45de8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java @@ -16,7 +16,7 @@ package org.springframework.boot.context.config; -import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.env.EnvironmentPostProcessor; /** @@ -28,10 +28,10 @@ import org.springframework.boot.env.EnvironmentPostProcessor; public interface ConfigDataLoaderContext { /** - * Provides access to the {@link BootstrapRegistry} shared across all + * Provides access to the {@link ConfigurableBootstrapContext} shared across all * {@link EnvironmentPostProcessor EnvironmentPostProcessors}. - * @return the bootstrap registry + * @return the bootstrap context */ - BootstrapRegistry getBootstrapRegistry(); + ConfigurableBootstrapContext getBootstrapContext(); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java index 3688649da5b..e7cc4e1fc52 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java @@ -23,6 +23,9 @@ import java.util.List; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.util.Instantiator; import org.springframework.core.ResolvableType; @@ -48,27 +51,36 @@ class ConfigDataLoaders { /** * Create a new {@link ConfigDataLoaders} instance. + * @param logFactory the deferred log factory + * @param bootstrapContext the bootstrap context * @param locationNotFoundAction the action to take if a * {@link ConfigDataLocationNotFoundException} is thrown - * @param logFactory the deferred log factory */ - ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction) { - this(logFactory, locationNotFoundAction, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null)); + ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, + ConfigDataLocationNotFoundAction locationNotFoundAction) { + this(logFactory, bootstrapContext, locationNotFoundAction, + SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null)); } /** * Create a new {@link ConfigDataLoaders} instance. * @param logFactory the deferred log factory + * @param bootstrapContext the bootstrap context * @param locationNotFoundAction the action to take if a * {@link ConfigDataLocationNotFoundException} is thrown * @param names the {@link ConfigDataLoader} class names instantiate */ - ConfigDataLoaders(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, - List names) { + ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, + ConfigDataLocationNotFoundAction locationNotFoundAction, List names) { this.logger = logFactory.getLog(getClass()); this.locationNotFoundAction = locationNotFoundAction; Instantiator> instantiator = new Instantiator<>(ConfigDataLoader.class, - (availableParameters) -> availableParameters.add(Log.class, logFactory::getLog)); + (availableParameters) -> { + availableParameters.add(Log.class, logFactory::getLog); + availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext); + availableParameters.add(BootstrapContext.class, bootstrapContext); + availableParameters.add(BootstrapRegistry.class, bootstrapContext); + }); this.loaders = instantiator.instantiate(names); this.locationTypes = getLocationTypes(this.loaders); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java index ab18ac3daee..a29e88196a6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java @@ -21,6 +21,9 @@ import java.util.List; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -36,6 +39,9 @@ import org.springframework.core.io.ResourceLoader; *

  • {@link Binder} - if the resolver needs to obtain values from the initial * {@link Environment}
  • *
  • {@link ResourceLoader} - if the resolver needs a resource loader
  • + *
  • {@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).
  • * *

    * Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java index eadd8a26581..fa8a8bedc4d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java @@ -16,8 +16,8 @@ package org.springframework.boot.context.config; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.origin.Origin; @@ -45,11 +45,11 @@ public interface ConfigDataLocationResolverContext { ConfigDataLocation getParent(); /** - * Provides access to the {@link BootstrapRegistry} shared across all + * Provides access to the {@link ConfigurableBootstrapContext} shared across all * {@link EnvironmentPostProcessor EnvironmentPostProcessors}. - * @return the bootstrap registry + * @return the bootstrap context */ - BootstrapRegistry getBootstrapRegistry(); + ConfigurableBootstrapContext getBootstrapContext(); /** * Return the {@link Origin} of a location that's being resolved. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java index 422d936a142..6b07ffb14e8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java @@ -23,6 +23,9 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.util.Instantiator; @@ -50,28 +53,31 @@ class ConfigDataLocationResolvers { /** * Create a new {@link ConfigDataLocationResolvers} instance. * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances + * @param bootstrapContext the bootstrap context * @param locationNotFoundAction the action to take if a * {@link ConfigDataLocationNotFoundException} is thrown * @param binder a binder providing values from the initial {@link Environment} * @param resourceLoader {@link ResourceLoader} to load resource locations */ - ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, - Binder binder, ResourceLoader resourceLoader) { - this(logFactory, locationNotFoundAction, binder, resourceLoader, + ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, + ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { + this(logFactory, bootstrapContext, locationNotFoundAction, binder, resourceLoader, SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null)); } /** * Create a new {@link ConfigDataLocationResolvers} instance. * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances + * @param bootstrapContext the bootstrap context * @param locationNotFoundAction the action to take if a * {@link ConfigDataLocationNotFoundException} is thrown * @param binder {@link Binder} providing values from the initial {@link Environment} * @param resourceLoader {@link ResourceLoader} to load resource locations * @param names the {@link ConfigDataLocationResolver} class names */ - ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigDataLocationNotFoundAction locationNotFoundAction, - Binder binder, ResourceLoader resourceLoader, List names) { + ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, + ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader, + List names) { this.logger = logFactory.getLog(getClass()); this.locationNotFoundAction = locationNotFoundAction; Instantiator> instantiator = new Instantiator<>(ConfigDataLocationResolver.class, @@ -79,6 +85,9 @@ class ConfigDataLocationResolvers { availableParameters.add(Log.class, logFactory::getLog); availableParameters.add(Binder.class, binder); availableParameters.add(ResourceLoader.class, resourceLoader); + availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext); + availableParameters.add(BootstrapContext.class, bootstrapContext); + availableParameters.add(BootstrapRegistry.class, bootstrapContext); }); this.resolvers = reorder(instantiator.instantiate(names)); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.java index 167dfa64240..00d24f8b1a7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.context.event; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -30,6 +31,8 @@ import org.springframework.core.env.Environment; @SuppressWarnings("serial") public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent { + private final ConfigurableBootstrapContext bootstrapContext; + private final ConfigurableEnvironment environment; /** @@ -37,13 +40,38 @@ public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent * @param application the current application * @param args the arguments the application is running with * @param environment the environment that was just created + * @deprecated since 2.4.0 in favor of + * {@link #ApplicationEnvironmentPreparedEvent(ConfigurableBootstrapContext, SpringApplication, String[], ConfigurableEnvironment)} */ + @Deprecated public ApplicationEnvironmentPreparedEvent(SpringApplication application, String[] args, ConfigurableEnvironment environment) { + this(null, application, args, environment); + } + + /** + * Create a new {@link ApplicationEnvironmentPreparedEvent} instance. + * @param bootstrapContext the bootstrap context + * @param application the current application + * @param args the arguments the application is running with + * @param environment the environment that was just created + */ + public ApplicationEnvironmentPreparedEvent(ConfigurableBootstrapContext bootstrapContext, + SpringApplication application, String[] args, ConfigurableEnvironment environment) { super(application, args); + this.bootstrapContext = bootstrapContext; this.environment = environment; } + /** + * Return the bootstap context. + * @return the bootstrap context + * @since 2.4.0 + */ + public ConfigurableBootstrapContext getBootstrapContext() { + return this.bootstrapContext; + } + /** * Return the environment. * @return the environment diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartingEvent.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartingEvent.java index e6587420099..b3b66132d35 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartingEvent.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartingEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.context.event; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -35,13 +36,39 @@ import org.springframework.core.env.Environment; @SuppressWarnings("serial") public class ApplicationStartingEvent extends SpringApplicationEvent { + private final ConfigurableBootstrapContext bootstrapContext; + /** * Create a new {@link ApplicationStartingEvent} instance. * @param application the current application * @param args the arguments the application is running with + * @deprecated since 2.4.0 in favor of + * {@link #ApplicationStartingEvent(ConfigurableBootstrapContext, SpringApplication, String[])} */ + @Deprecated public ApplicationStartingEvent(SpringApplication application, String[] args) { + this(null, application, args); + } + + /** + * Create a new {@link ApplicationStartingEvent} instance. + * @param bootstrapContext the bootstrap context + * @param application the current application + * @param args the arguments the application is running with + */ + public ApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext, SpringApplication application, + String[] args) { super(application, args); + this.bootstrapContext = bootstrapContext; + } + + /** + * Return the bootstap context. + * @return the bootstrap context + * @since 2.4.0 + */ + public ConfigurableBootstrapContext getBootstrapContext() { + return this.bootstrapContext; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java index bd095161a19..fcc94af3850 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java @@ -19,6 +19,7 @@ package org.springframework.boot.context.event; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationRunListener; import org.springframework.boot.availability.AvailabilityChangeEvent; @@ -70,14 +71,16 @@ public class EventPublishingRunListener implements SpringApplicationRunListener, } @Override - public void starting() { - this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); + public void starting(ConfigurableBootstrapContext bootstrapContext) { + this.initialMulticaster + .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args)); } @Override - public void environmentPrepared(ConfigurableEnvironment environment) { - this.initialMulticaster - .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); + public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, + ConfigurableEnvironment environment) { + this.initialMulticaster.multicastEvent( + new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment)); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/BootstrapRegistry.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/BootstrapRegistry.java deleted file mode 100644 index 030c1ee56de..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/BootstrapRegistry.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -import org.springframework.boot.context.event.ApplicationPreparedEvent; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.Environment; - -/** - * A simple object registry that is available during {@link Environment} post-processing - * up to the point that the {@link ApplicationContext} is prepared. The registry can be - * used to store objects that may be expensive to create, or need to be shared by - * different {@link EnvironmentPostProcessor EnvironmentPostProcessors}. - *

    - * The registry uses the object type as a key, meaning that only a single instance of a - * given class can be stored. - *

    - * Registered instances may optionally use - * {@link Registration#onApplicationContextPrepared(BiConsumer) - * onApplicationContextPrepared(...)} to perform an action when the - * {@link ApplicationContext} is {@link ApplicationPreparedEvent prepared}. For example, - * an instance may choose to register itself as a regular Spring bean so that it is - * available for the application to use. - * - * @author Phillip Webb - * @since 2.4.0 - * @see EnvironmentPostProcessor - */ -public interface BootstrapRegistry { - - /** - * Get an instance from the registry, creating one if it does not already exist. - * @param the instance type - * @param type the instance type - * @param instanceSupplier a supplier used to create the instance if it doesn't - * already exist - * @return the registered instance - */ - T get(Class type, Supplier instanceSupplier); - - /** - * Get an instance from the registry, creating one if it does not already exist. - * @param the instance type - * @param type the instance type - * @param instanceSupplier a supplier used to create the instance if it doesn't - * already exist - * @param onApplicationContextPreparedAction the action that should be called when the - * application context is prepared. This action is ignored if the registration already - * exists. - * @return the registered instance - */ - T get(Class type, Supplier instanceSupplier, - BiConsumer onApplicationContextPreparedAction); - - /** - * Register an instance with the registry and return a {@link Registration} that can - * be used to provide further configuration. This method will replace any existing - * registration. - * @param the instance type - * @param type the instance type - * @param instanceSupplier a supplier used to create the instance if it doesn't - * already exist - * @return an instance registration - */ - Registration register(Class type, Supplier instanceSupplier); - - /** - * Return if a registration exists for the given type. - * @param the instance type - * @param type the instance type - * @return {@code true} if the type has already been registered - */ - boolean isRegistered(Class type); - - /** - * Return any existing {@link Registration} for the given type. - * @param the instance type - * @param type the instance type - * @return the existing registration or {@code null} - */ - Registration getRegistration(Class type); - - /** - * A single registration contained in the registry. - * - * @param the instance type - */ - interface Registration { - - /** - * Get or crearte the registered object instance. - * @return the object instance - */ - T get(); - - /** - * Add an action that should run when the {@link ApplicationContext} has been - * prepared. - * @param action the action to run - */ - void onApplicationContextPrepared(BiConsumer action); - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java deleted file mode 100644 index b1c214cdf57..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * Default implementation of {@link BootstrapRegistry}. - * - * @author Phillip Webb - * @since 2.4.0 - */ -public class DefaultBootstrapRegisty implements BootstrapRegistry { - - private final Map, DefaultRegistration> registrations = new HashMap<>(); - - @Override - public T get(Class type, Supplier instanceSupplier) { - return get(type, instanceSupplier, null); - } - - @Override - public T get(Class type, Supplier instanceSupplier, - BiConsumer onApplicationContextPreparedAction) { - Registration registration = getRegistration(type); - if (registration != null) { - return registration.get(); - } - registration = register(type, instanceSupplier); - registration.onApplicationContextPrepared(onApplicationContextPreparedAction); - return registration.get(); - } - - @Override - public Registration register(Class type, Supplier instanceSupplier) { - DefaultRegistration registration = new DefaultRegistration<>(instanceSupplier); - this.registrations.put(type, registration); - return registration; - } - - @Override - public boolean isRegistered(Class type) { - return getRegistration(type) != null; - } - - @Override - @SuppressWarnings("unchecked") - public Registration getRegistration(Class type) { - return (Registration) 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 implements Registration { - - private Supplier instanceSupplier; - - private volatile T instance; - - private List> applicationContextPreparedActions = new ArrayList<>(); - - DefaultRegistration(Supplier 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 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())); - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java index a9f6b47af5f..1ef5cf4e91d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessor.java @@ -18,6 +18,9 @@ package org.springframework.boot.env; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; @@ -41,8 +44,9 @@ import org.springframework.core.env.Environment; * itself to configure logging levels). *

  • {@link Log} - A log with output deferred until the application has been full * prepared (allowing the environment itself to configure logging levels).
  • - *
  • {@link BootstrapRegistry} - A bootstrap registry that can be used to store objects - * that may be expensive to create, or need to be shared.
  • + *
  • {@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).
  • * * * @author Andy Wilkinson diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java index 2e17928033f..e255031cca1 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java @@ -18,6 +18,7 @@ package org.springframework.boot.env; import java.util.List; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent; @@ -44,8 +45,6 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica private final DeferredLogs deferredLogs; - private final DefaultBootstrapRegisty bootstrapRegistry; - private int order = DEFAULT_ORDER; private final EnvironmentPostProcessorsFactory postProcessorsFactory; @@ -65,14 +64,13 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica * @param postProcessorsFactory the post processors factory */ public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) { - this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty()); + this(postProcessorsFactory, new DeferredLogs()); } EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory, - DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) { + DeferredLogs deferredLogs) { this.postProcessorsFactory = postProcessorsFactory; this.deferredLogs = deferredLogs; - this.bootstrapRegistry = bootstrapRegistry; } @Override @@ -98,13 +96,12 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); SpringApplication application = event.getSpringApplication(); - for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors()) { + for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) { postProcessor.postProcessEnvironment(environment, application); } } private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { - this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext()); finish(); } @@ -113,12 +110,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica } private void finish() { - this.bootstrapRegistry.clear(); this.deferredLogs.switchOverAll(); } - List getEnvironmentPostProcessors() { - return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry); + List getEnvironmentPostProcessors(ConfigurableBootstrapContext bootstrapContext) { + return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java index 1074ccace03..dbd3730c9fc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java @@ -18,6 +18,7 @@ package org.springframework.boot.env; import java.util.List; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -34,11 +35,11 @@ public interface EnvironmentPostProcessorsFactory { /** * Create all requested {@link EnvironmentPostProcessor} instances. * @param logFactory a deferred log factory - * @param bootstrapRegistry a bootstrap registry + * @param bootstrapContext a bootstrap context * @return the post processor instances */ List getEnvironmentPostProcessors(DeferredLogFactory logFactory, - BootstrapRegistry bootstrapRegistry); + ConfigurableBootstrapContext bootstrapContext); /** * Return a {@link EnvironmentPostProcessorsFactory} backed by diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java index baab7439ddb..7b984ff3aef 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java @@ -21,6 +21,9 @@ import java.util.List; import org.apache.commons.logging.Log; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.util.Instantiator; @@ -48,12 +51,14 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce @Override public List getEnvironmentPostProcessors(DeferredLogFactory logFactory, - BootstrapRegistry bootstrapRegistry) { + ConfigurableBootstrapContext bootstrapContext) { Instantiator instantiator = new Instantiator<>(EnvironmentPostProcessor.class, (parameters) -> { parameters.add(DeferredLogFactory.class, logFactory); parameters.add(Log.class, logFactory::getLog); - parameters.add(BootstrapRegistry.class, bootstrapRegistry); + parameters.add(ConfigurableBootstrapContext.class, bootstrapContext); + parameters.add(BootstrapContext.class, bootstrapContext); + parameters.add(BootstrapRegistry.class, bootstrapContext); }); return instantiator.instantiate(this.classNames); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/DefaultBootstrapContextTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/DefaultBootstrapContextTests.java new file mode 100644 index 00000000000..e6003dc704f --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/DefaultBootstrapContextTests.java @@ -0,0 +1,223 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.AssertProvider; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.StaticApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link DefaultBootstrapContext}. + * + * @author Phillip Webb + */ +class DefaultBootstrapContextTests { + + private DefaultBootstrapContext context = new DefaultBootstrapContext(); + + private AtomicInteger counter = new AtomicInteger(); + + private StaticApplicationContext applicationContext = new StaticApplicationContext(); + + @Test + void registerWhenTypeIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(null, InstanceSupplier.of(1))) + .withMessage("Type must not be null"); + } + + @Test + void registerWhenRegistrationIsNullThrowException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.context.register(Integer.class, null)) + .withMessage("InstanceSupplier must not be null"); + } + + @Test + void registerWhenNotAlreadyRegisteredRegistersInstance() { + this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement)); + assertThat(this.context.get(Integer.class)).isEqualTo(0); + assertThat(this.context.get(Integer.class)).isEqualTo(0); + } + + @Test + void registerWhenAlreadyRegisteredRegistersReplacedInstance() { + this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement)); + this.context.register(Integer.class, InstanceSupplier.of(100)); + assertThat(this.context.get(Integer.class)).isEqualTo(100); + assertThat(this.context.get(Integer.class)).isEqualTo(100); + } + + @Test + void registerWhenAlreadyCreatedThrowsException() { + this.context.register(Integer.class, InstanceSupplier.from(this.counter::getAndIncrement)); + this.context.get(Integer.class); + assertThatIllegalStateException() + .isThrownBy(() -> this.context.register(Integer.class, InstanceSupplier.of(100))) + .withMessage("java.lang.Integer has already been created"); + } + + @Test + void registerWithDependencyRegistersInstance() { + this.context.register(Integer.class, InstanceSupplier.of(100)); + this.context.register(String.class, this::integerAsString); + assertThat(this.context.get(String.class)).isEqualTo("100"); + } + + private String integerAsString(BootstrapContext context) { + return String.valueOf(context.get(Integer.class)); + } + + @Test + void registerIfAbsentWhenAbsentRegisters() { + this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L)); + assertThat(this.context.get(Long.class)).isEqualTo(100L); + } + + @Test + void registerIfAbsentWhenPresentDoesNotRegister() { + this.context.registerIfAbsent(Long.class, InstanceSupplier.of(1L)); + this.context.registerIfAbsent(Long.class, InstanceSupplier.of(100L)); + assertThat(this.context.get(Long.class)).isEqualTo(1L); + } + + @Test + void isRegisteredWhenNotRegisteredReturnsFalse() { + this.context.register(Number.class, InstanceSupplier.of(1)); + assertThat(this.context.isRegistered(Long.class)).isFalse(); + } + + @Test + void isRegisteredWhenRegisteredReturnsTrue() { + this.context.register(Number.class, InstanceSupplier.of(1)); + assertThat(this.context.isRegistered(Number.class)).isTrue(); + } + + @Test + void getRegisteredInstanceSupplierWhenNotRegisteredReturnsNull() { + this.context.register(Number.class, InstanceSupplier.of(1)); + assertThat(this.context.getRegisteredInstanceSupplier(Long.class)).isNull(); + } + + @Test + void getRegisteredInstanceSupplierWhenRegisteredReturnsRegistration() { + InstanceSupplier 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, AssertProvider { + + 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 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; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 5541743f0e3..1610eb5ddbc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -48,6 +48,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultBeanNameGenerator; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.AvailabilityState; import org.springframework.boot.availability.LivenessState; @@ -105,6 +106,8 @@ import org.springframework.core.metrics.ApplicationStartup; import org.springframework.core.metrics.StartupStep; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.WebApplicationContext; @@ -1201,6 +1204,35 @@ class SpringApplicationTests { assertThat(startCount).isEqualTo(endCount); } + @Test + void addBootstrapper() { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapper( + (bootstrapContext) -> bootstrapContext.register(String.class, InstanceSupplier.of("boot"))); + TestApplicationListener listener = new TestApplicationListener(); + application.addListeners(listener); + application.run(); + ApplicationStartingEvent startingEvent = listener.getEvent(ApplicationStartingEvent.class); + assertThat(startingEvent.getBootstrapContext().get(String.class)); + ApplicationEnvironmentPreparedEvent environmentPreparedEvent = listener + .getEvent(ApplicationEnvironmentPreparedEvent.class); + assertThat(environmentPreparedEvent.getBootstrapContext().get(String.class)); + } + + @Test + void addBootstrapperCanRegisterBeans() { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapper((bootstrapContext) -> { + bootstrapContext.register(String.class, InstanceSupplier.of("boot")); + bootstrapContext.addCloseListener((event) -> event.getApplicationContext().getBeanFactory() + .registerSingleton("test", event.getBootstrapContext().get(String.class))); + }); + ConfigurableApplicationContext applicationContext = application.run(); + assertThat(applicationContext.getBean("test")).isEqualTo("boot"); + } + private ArgumentMatcher isAvailabilityChangeEventWithState( S state) { return (argument) -> (argument instanceof AvailabilityChangeEvent) @@ -1658,4 +1690,20 @@ class SpringApplicationTests { } + static class TestApplicationListener implements ApplicationListener { + + private final MultiValueMap, ApplicationEvent> events = new LinkedMultiValueMap<>(); + + @Override + public void onApplicationEvent(ApplicationEvent event) { + this.events.add(event.getClass(), event); + } + + @SuppressWarnings("unchecked") + E getEvent(Class type) { + return (E) this.events.get(type).get(0); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java index 43b657ddf87..de6139cb6b5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java @@ -282,6 +282,15 @@ class SpringApplicationBuilderTests { this.context.getBean(ChildConfig.class); } + @Test + void addBootstrapper() { + SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class) + .web(WebApplicationType.NONE).addBootstrapper((context) -> context.addCloseListener( + (event) -> event.getApplicationContext().getBeanFactory().registerSingleton("test", "spring"))); + this.context = application.run(); + assertThat(this.context.getBean("test")).isEqualTo("spring"); + } + @Configuration(proxyBeanMethods = false) static class ExampleConfig { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java index c4bd0600c22..5a62022ce55 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/ApplicationPidFileWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; @@ -123,7 +124,8 @@ class ApplicationPidFileWriterTests { File file = new File(this.tempDir, "pid"); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); listener.setTriggerEventType(ApplicationStartingEvent.class); - listener.onApplicationEvent(new ApplicationStartingEvent(new SpringApplication(), new String[] {})); + listener.onApplicationEvent( + new ApplicationStartingEvent(new DefaultBootstrapContext(), new SpringApplication(), new String[] {})); assertThat(contentOf(file)).isNotEmpty(); } @@ -170,7 +172,8 @@ class ApplicationPidFileWriterTests { private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, String propValue) { ConfigurableEnvironment environment = createEnvironment(propName, propValue); - return new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[] {}, environment); + return new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(), new SpringApplication(), + new String[] {}, environment); } private SpringApplicationEvent createPreparedEvent(String propName, String propValue) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/FileEncodingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/FileEncodingApplicationListenerTests.java index 0c70e535323..0a4a6f220c3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/FileEncodingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/FileEncodingApplicationListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.boot.context; import org.junit.jupiter.api.Test; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; @@ -40,7 +41,7 @@ class FileEncodingApplicationListenerTests { private final ConfigurableEnvironment environment = new StandardEnvironment(); private final ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent( - new SpringApplication(), new String[0], this.environment); + new DefaultBootstrapContext(), new SpringApplication(), new String[0], this.environment); @Test void testIllegalState() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java index a10fe120914..3225b68ecc8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java @@ -30,13 +30,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.env.BootstrapRegistry; -import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockPropertySource; @@ -60,7 +59,7 @@ class ConfigDataEnvironmentContributorsTests { private DeferredLogFactory logFactory = Supplier::get; - private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); private MockEnvironment environment; @@ -80,9 +79,10 @@ class ConfigDataEnvironmentContributorsTests { void setup() { this.environment = new MockEnvironment(); this.binder = Binder.get(this.environment); - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, null); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL); + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, + ConfigDataLocationNotFoundAction.FAIL); this.importer = new ConfigDataImporter(resolvers, loaders); this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null); } @@ -91,7 +91,7 @@ class ConfigDataEnvironmentContributorsTests { void createCreatesWithInitialContributors() { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); Iterator iterator = contributors.iterator(); assertThat(iterator.next()).isSameAs(contributor); assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT); @@ -102,7 +102,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofExisting(new MockPropertySource()); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); assertThat(withProcessedImports).isSameAs(contributors); @@ -119,7 +119,7 @@ class ConfigDataEnvironmentContributorsTests { .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); Iterator iterator = withProcessedImports.iterator(); @@ -148,7 +148,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofInitialImport("initialimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); Iterator iterator = withProcessedImports.iterator(); @@ -174,7 +174,7 @@ class ConfigDataEnvironmentContributorsTests { .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); + this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any()); ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); @@ -200,7 +200,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofInitialImport("initialimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); contributors.withProcessedImports(this.importer, this.activationContext); verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations)); ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); @@ -222,11 +222,11 @@ class ConfigDataEnvironmentContributorsTests { .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); + this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any()); ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); - assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry); + assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext); } @Test @@ -244,11 +244,11 @@ class ConfigDataEnvironmentContributorsTests { .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); + this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any()); ConfigDataLoaderContext context = this.loaderContext.getValue(); - assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry); + assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext); } @Test @@ -257,7 +257,7 @@ class ConfigDataEnvironmentContributorsTests { propertySource.setProperty("test", "springboot"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot"); } @@ -272,7 +272,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("one"); } @@ -288,7 +288,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("two"); } @@ -300,7 +300,7 @@ class ConfigDataEnvironmentContributorsTests { propertySource.setProperty("other", "springboot"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(contributor)); + this.bootstrapContext, Arrays.asList(contributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot"); } @@ -317,7 +317,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("two"); } @@ -333,7 +333,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); @@ -350,7 +350,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); @@ -368,7 +368,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); + this.bootstrapContext, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java index 9c0b375a963..bf6a8a3d0ff 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java @@ -19,10 +19,10 @@ package org.springframework.boot.context.config; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.context.config.TestConfigDataBootstrap.LoaderHelper; -import org.springframework.boot.env.BootstrapRegistry; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java index 01d0278059e..fbf67a693c8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java @@ -27,8 +27,8 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; @@ -60,7 +60,7 @@ class ConfigDataEnvironmentPostProcessorTests { @Spy private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get, - new DefaultBootstrapRegisty()); + new DefaultBootstrapContext()); @Captor private ArgumentCaptor> additionalProfilesCaptor; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java index 63b11f4abd7..be14fb81b38 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java @@ -26,11 +26,11 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.env.BootstrapRegistry; -import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; @@ -52,7 +52,7 @@ class ConfigDataEnvironmentTests { private DeferredLogFactory logFactory = Supplier::get; - private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); private MockEnvironment environment = new MockEnvironment(); @@ -64,7 +64,7 @@ class ConfigDataEnvironmentTests { void createWhenUseLegacyPropertyInEnvironmentThrowsException() { this.environment.setProperty("spring.config.use-legacy-processing", "true"); assertThatExceptionOfType(UseLegacyConfigProcessingException.class) - .isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, this.environment, + .isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles)); } @@ -72,7 +72,7 @@ class ConfigDataEnvironmentTests { void createExposesEnvironmentBinderToConfigDataLocationResolvers() { this.environment.setProperty("spring", "boot"); TestConfigDataEnvironment configDataEnvironment = new TestConfigDataEnvironment(this.logFactory, - this.bootstrapRegistry, this.environment, this.resourceLoader, this.additionalProfiles); + this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); assertThat(configDataEnvironment.getConfigDataLocationResolversBinder().bind("spring", String.class).get()) .isEqualTo("boot"); } @@ -85,7 +85,7 @@ class ConfigDataEnvironmentTests { this.environment.getPropertySources().addLast(propertySource1); this.environment.getPropertySources().addLast(propertySource2); this.environment.getPropertySources().addLast(propertySource3); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); @@ -104,7 +104,7 @@ class ConfigDataEnvironmentTests { this.environment.getPropertySources().addLast(defaultPropertySource); this.environment.getPropertySources().addLast(propertySource1); this.environment.getPropertySources().addLast(propertySource2); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); @@ -120,7 +120,7 @@ class ConfigDataEnvironmentTests { this.environment.setProperty("spring.config.location", "l1,l2"); this.environment.setProperty("spring.config.additional-location", "a1,a2"); this.environment.setProperty("spring.config.import", "i1,i2"); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); @@ -132,7 +132,7 @@ class ConfigDataEnvironmentTests { @Test void processAndApplyAddsImportedSourceToEnvironment(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); @@ -141,7 +141,7 @@ class ConfigDataEnvironmentTests { @Test void processAndApplyOnlyAddsActiveContributors(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); @@ -153,7 +153,7 @@ class ConfigDataEnvironmentTests { MockPropertySource defaultPropertySource = new MockPropertySource("defaultProperties"); this.environment.getPropertySources().addFirst(defaultPropertySource); this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); List> sources = this.environment.getPropertySources().stream().collect(Collectors.toList()); @@ -163,7 +163,7 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsDefaultProfiles(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getDefaultProfiles()).containsExactly("one", "two", "three"); @@ -172,7 +172,7 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsActiveProfiles(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getActiveProfiles()).containsExactly("one", "two", "three"); @@ -181,7 +181,7 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsActiveProfilesAndProfileGroups(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getActiveProfiles()).containsExactly("one", "four", "five", "two", "three"); @@ -191,7 +191,7 @@ class ConfigDataEnvironmentTests { @Disabled("Disabled until spring.profiles support is dropped") void processAndApplyWhenHasInvalidPropertyThrowsException() { this.environment.setProperty("spring.profile", "a"); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, this.environment, this.resourceLoader, this.additionalProfiles); assertThatExceptionOfType(InvalidConfigDataPropertyException.class) .isThrownBy(() -> configDataEnvironment.processAndApply()); @@ -206,17 +206,19 @@ class ConfigDataEnvironmentTests { private Binder configDataLocationResolversBinder; - TestConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, + TestConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles) { - super(logFactory, bootstrapRegistry, environment, resourceLoader, additionalProfiles); + super(logFactory, bootstrapContext, environment, resourceLoader, additionalProfiles); } @Override protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, - ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { + ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, + Binder binder, ResourceLoader resourceLoader) { this.configDataLocationResolversBinder = binder; - return super.createConfigDataLocationResolvers(logFactory, locationNotFoundAction, binder, resourceLoader); + return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, + resourceLoader); } Binder getConfigDataLocationResolversBinder() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java index b1e3b0f88b1..f69339508e1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java @@ -24,6 +24,11 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.junit.jupiter.api.Test; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockPropertySource; @@ -42,19 +47,28 @@ class ConfigDataLoadersTests { private DeferredLogFactory logFactory = Supplier::get; + private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class); @Test void createWhenLoaderHasLogParameterInjectsLog() { - new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, + new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(LoggingConfigDataLoader.class.getName())); } + @Test + void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() { + new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, + Arrays.asList(BootstrappingConfigDataLoader.class.getName())); + assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); + } + @Test void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception { TestConfigDataLocation location = new TestConfigDataLocation("test"); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, - Arrays.asList(TestConfigDataLoader.class.getName())); + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, + ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(TestConfigDataLoader.class.getName())); ConfigData loaded = loaders.load(this.context, location); assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class); } @@ -62,7 +76,8 @@ class ConfigDataLoadersTests { @Test void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception { TestConfigDataLocation location = new TestConfigDataLocation("test"); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, + ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName())); assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) .withMessageContaining("Multiple loaders found for location test"); @@ -71,8 +86,8 @@ class ConfigDataLoadersTests { @Test void loadWhenNoLoaderSupportsLocationThrowsException() { TestConfigDataLocation location = new TestConfigDataLocation("test"); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, - Arrays.asList(NonLoadableConfigDataLoader.class.getName())); + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, + ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(NonLoadableConfigDataLoader.class.getName())); assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) .withMessage("No loader found for location 'test'"); } @@ -80,7 +95,8 @@ class ConfigDataLoadersTests { @Test void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception { TestConfigDataLocation location = new TestConfigDataLocation("test"); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, ConfigDataLocationNotFoundAction.FAIL, + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, + ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName())); ConfigData loaded = loaders.load(this.context, location); assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class); @@ -131,6 +147,24 @@ class ConfigDataLoadersTests { } + static class BootstrappingConfigDataLoader implements ConfigDataLoader { + + 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 { @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java index f699860bd8f..7e0171be6a9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java @@ -28,6 +28,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.Ordered; @@ -50,6 +55,8 @@ class ConfigDataLocationResolversTests { private DeferredLogFactory logFactory = Supplier::get; + private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + @Mock private Binder binder; @@ -63,7 +70,7 @@ class ConfigDataLocationResolversTests { @Test void createWhenInjectingBinderCreatesResolver() { - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName())); assertThat(resolvers.getResolvers()).hasSize(1); @@ -73,17 +80,24 @@ class ConfigDataLocationResolversTests { @Test void createWhenNotInjectingBinderCreatesResolver() { - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Collections.singletonList(TestResolver.class.getName())); assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class); } + @Test + void createWhenResolverHasBootstrapParametersInjectsBootstrapContext() { + new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, + this.binder, this.resourceLoader, Collections.singletonList(TestBootstrappingResolver.class.getName())); + assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); + } + @Test void createWhenNameIsNotConfigDataLocationResolverThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, + .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Collections.singletonList(InputStream.class.getName()))) .withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable"); @@ -95,7 +109,7 @@ class ConfigDataLocationResolversTests { names.add(TestResolver.class.getName()); names.add(LowestTestResolver.class.getName()); names.add(HighestTestResolver.class.getName()); - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, names); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class); assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class); @@ -104,7 +118,7 @@ class ConfigDataLocationResolversTests { @Test void resolveAllResolvesUsingFirstSupportedResolver() { - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); List resolved = resolvers.resolveAll(this.context, @@ -118,7 +132,7 @@ class ConfigDataLocationResolversTests { @Test void resolveAllWhenProfileMergesResolvedLocations() { - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); List resolved = resolvers.resolveAll(this.context, @@ -136,7 +150,7 @@ class ConfigDataLocationResolversTests { @Test void resolveWhenNoResolverThrowsException() { - ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, + ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); assertThatExceptionOfType(UnsupportedConfigDataLocationException.class) @@ -181,6 +195,19 @@ class ConfigDataLocationResolversTests { } + static class TestBootstrappingResolver extends TestResolver { + + TestBootstrappingResolver(ConfigurableBootstrapContext configurableBootstrapContext, + BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) { + assertThat(configurableBootstrapContext).isNotNull(); + assertThat(bootstrapRegistry).isNotNull(); + assertThat(bootstrapContext).isNotNull(); + assertThat(configurableBootstrapContext).isEqualTo(bootstrapRegistry).isEqualTo(bootstrapContext); + bootstrapRegistry.register(String.class, InstanceSupplier.of("boot")); + } + + } + @Order(Ordered.HIGHEST_PRECEDENCE) static class HighestTestResolver extends TestResolver { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java index 4859df83d78..61cf3b45e07 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/DelegatingApplicationListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.boot.context.config; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.context.ApplicationListener; @@ -53,8 +54,8 @@ class DelegatingApplicationListenerTests { void orderedInitialize() { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "context.listener.classes=" + MockInitB.class.getName() + "," + MockInitA.class.getName()); - this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], - this.context.getEnvironment())); + this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(), + new SpringApplication(), new String[0], this.context.getEnvironment())); this.context.getBeanFactory().registerSingleton("testListener", this.listener); this.context.refresh(); assertThat(this.context.getBeanFactory().getSingleton("a")).isEqualTo("a"); @@ -63,15 +64,15 @@ class DelegatingApplicationListenerTests { @Test void noInitializers() { - this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], - this.context.getEnvironment())); + this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(), + new SpringApplication(), new String[0], this.context.getEnvironment())); } @Test void emptyInitializers() { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "context.listener.classes:"); - this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new SpringApplication(), new String[0], - this.context.getEnvironment())); + this.listener.onApplicationEvent(new ApplicationEnvironmentPreparedEvent(new DefaultBootstrapContext(), + new SpringApplication(), new String[0], this.context.getEnvironment())); } @Order(Ordered.HIGHEST_PRECEDENCE) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java index cb739a1b1cb..6e0abfcc731 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java @@ -20,7 +20,9 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.boot.BootstrapContextClosedEvent; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.context.ApplicationListener; import org.springframework.core.env.MapPropertySource; /** @@ -42,8 +44,9 @@ class TestConfigDataBootstrap { @Override public List resolve(ConfigDataLocationResolverContext context, String location, boolean optional) { - ResolverHelper helper = context.getBootstrapRegistry().get(ResolverHelper.class, - () -> new ResolverHelper(location)); + context.getBootstrapContext().registerIfAbsent(ResolverHelper.class, + InstanceSupplier.from(() -> new ResolverHelper(location))); + ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class); return Collections.singletonList(new Location(helper)); } @@ -53,8 +56,10 @@ class TestConfigDataBootstrap { @Override public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException { - context.getBootstrapRegistry().get(LoaderHelper.class, () -> new LoaderHelper(location), - LoaderHelper::addToContext); + context.getBootstrapContext().registerIfAbsent(LoaderHelper.class, + InstanceSupplier.from(() -> new LoaderHelper(location))); + LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class); + context.getBootstrapContext().addCloseListener(helper); return new ConfigData( Collections.singleton(new MapPropertySource("loaded", Collections.singletonMap("test", "test")))); } @@ -94,7 +99,7 @@ class TestConfigDataBootstrap { } - static class LoaderHelper { + static class LoaderHelper implements ApplicationListener { private final Location location; @@ -106,8 +111,9 @@ class TestConfigDataBootstrap { return this.location; } - static void addToContext(ConfigurableApplicationContext context, LoaderHelper loaderHelper) { - context.getBeanFactory().registerSingleton("loaderHelper", loaderHelper); + @Override + public void onApplicationEvent(BootstrapContextClosedEvent event) { + event.getApplicationContext().getBeanFactory().registerSingleton("loaderHelper", this); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/event/EventPublishingRunListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/event/EventPublishingRunListenerTests.java index 877dd4f5e77..5f922fbbd71 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/event/EventPublishingRunListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/event/EventPublishingRunListenerTests.java @@ -25,6 +25,7 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.context.ApplicationEvent; @@ -42,6 +43,8 @@ import static org.mockito.Mockito.mock; */ class EventPublishingRunListenerTests { + private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + private SpringApplication application; private EventPublishingRunListener runListener; @@ -60,9 +63,9 @@ class EventPublishingRunListenerTests { void shouldPublishLifecycleEvents() { StaticApplicationContext context = new StaticApplicationContext(); assertThat(this.eventListener.receivedEvents()).isEmpty(); - this.runListener.starting(); + this.runListener.starting(this.bootstrapContext); checkApplicationEvents(ApplicationStartingEvent.class); - this.runListener.environmentPrepared(null); + this.runListener.environmentPrepared(this.bootstrapContext, null); checkApplicationEvents(ApplicationEnvironmentPreparedEvent.class); this.runListener.contextPrepared(context); checkApplicationEvents(ApplicationContextInitializedEvent.class); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index dadde0096cf..f72c008d3ea 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.io.TempDir; import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.impl.StaticLoggerBinder; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationStartingEvent; @@ -97,6 +98,8 @@ class LoggingApplicationListenerTests { private final ch.qos.logback.classic.Logger logger = this.loggerContext.getLogger(getClass()); + private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); + private final SpringApplication springApplication = new SpringApplication(); private final GenericApplicationContext context = new GenericApplicationContext(); @@ -113,7 +116,7 @@ class LoggingApplicationListenerTests { this.output = output; this.logFile = new File(this.tempDir.toFile(), "foo.log"); LogManager.getLogManager().readConfiguration(JavaLoggingSystem.class.getResourceAsStream("logging.properties")); - multicastEvent(new ApplicationStartingEvent(new SpringApplication(), NO_ARGS)); + multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS)); new File(this.tempDir.toFile(), "spring.log").delete(); ConfigurableEnvironment environment = this.context.getEnvironment(); ConfigurationPropertySources.attach(environment); @@ -372,7 +375,8 @@ class LoggingApplicationListenerTests { void parseArgsDoesntReplace() { this.initializer.setSpringBootLogging(LogLevel.ERROR); this.initializer.setParseArgs(false); - multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[] { "--debug" })); + multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, + new String[] { "--debug" })); this.initializer.initialize(this.context.getEnvironment(), this.context.getClassLoader()); this.logger.debug("testatdebug"); assertThat(this.output).doesNotContain("testatdebug"); @@ -406,7 +410,7 @@ class LoggingApplicationListenerTests { void shutdownHookIsNotRegisteredByDefault() { TestLoggingApplicationListener listener = new TestLoggingApplicationListener(); System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName()); - multicastEvent(listener, new ApplicationStartingEvent(new SpringApplication(), NO_ARGS)); + multicastEvent(listener, new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS)); listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); assertThat(listener.shutdownHook).isNull(); } @@ -416,7 +420,7 @@ class LoggingApplicationListenerTests { TestLoggingApplicationListener listener = new TestLoggingApplicationListener(); System.setProperty(LoggingSystem.class.getName(), TestShutdownHandlerLoggingSystem.class.getName()); addPropertiesToEnvironment(this.context, "logging.register_shutdown_hook=true"); - multicastEvent(listener, new ApplicationStartingEvent(new SpringApplication(), NO_ARGS)); + multicastEvent(listener, new ApplicationStartingEvent(this.bootstrapContext, new SpringApplication(), NO_ARGS)); listener.initialize(this.context.getEnvironment(), this.context.getClassLoader()); assertThat(listener.shutdownHook).isNotNull(); listener.shutdownHook.start(); @@ -426,7 +430,7 @@ class LoggingApplicationListenerTests { @Test void closingContextCleansUpLoggingSystem() { System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); - multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0])); + multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0])); TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils .getField(this.initializer, "loggingSystem"); assertThat(loggingSystem.cleanedUp).isFalse(); @@ -437,7 +441,7 @@ class LoggingApplicationListenerTests { @Test void closingChildContextDoesNotCleanUpLoggingSystem() { System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); - multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0])); + multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0])); TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils .getField(this.initializer, "loggingSystem"); assertThat(loggingSystem.cleanedUp).isFalse(); @@ -496,7 +500,7 @@ class LoggingApplicationListenerTests { @Test void applicationFailedEventCleansUpLoggingSystem() { System.setProperty(LoggingSystem.SYSTEM_PROPERTY, TestCleanupLoggingSystem.class.getName()); - multicastEvent(new ApplicationStartingEvent(this.springApplication, new String[0])); + multicastEvent(new ApplicationStartingEvent(this.bootstrapContext, this.springApplication, new String[0])); TestCleanupLoggingSystem loggingSystem = (TestCleanupLoggingSystem) ReflectionTestUtils .getField(this.initializer, "loggingSystem"); assertThat(loggingSystem.cleanedUp).isFalse(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/DefaultBootstrapRegistyTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/DefaultBootstrapRegistyTests.java deleted file mode 100644 index b4faf99c12a..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/DefaultBootstrapRegistyTests.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.env; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.assertj.core.api.AbstractAssert; -import org.assertj.core.api.AssertProvider; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.env.BootstrapRegistry.Registration; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.StaticApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for {@link DefaultBootstrapRegisty}. - * - * @author Phillip Webb - */ -class DefaultBootstrapRegistyTests { - - private DefaultBootstrapRegisty registy = new DefaultBootstrapRegisty(); - - private AtomicInteger counter = new AtomicInteger(); - - private StaticApplicationContext context = new StaticApplicationContext(); - - @Test - void getWhenNotRegisteredCreateInstance() { - Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement); - assertThat(result).isEqualTo(0); - } - - @Test - void getWhenAlreadyRegisteredReturnsExisting() { - this.registy.get(Integer.class, this.counter::getAndIncrement); - Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement); - assertThat(result).isEqualTo(0); - } - - @Test - void getWithPreparedActionRegistersAction() { - TestApplicationPreparedAction action = new TestApplicationPreparedAction(); - Integer result = this.registy.get(Integer.class, this.counter::getAndIncrement, action::run); - this.registy.applicationContextPrepared(this.context); - assertThat(result).isEqualTo(0); - assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0); - } - - @Test - void getWithPreparedActionWhenAlreadyRegisteredIgnoresRegistersAction() { - TestApplicationPreparedAction action1 = new TestApplicationPreparedAction(); - TestApplicationPreparedAction action2 = new TestApplicationPreparedAction(); - this.registy.get(Integer.class, this.counter::getAndIncrement, action1::run); - this.registy.get(Integer.class, this.counter::getAndIncrement, action2::run); - this.registy.applicationContextPrepared(this.context); - assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0); - assertThat(action2).wasNotCalled(); - } - - @Test - void registerAddsRegistration() { - Registration registration = this.registy.register(Integer.class, this.counter::getAndIncrement); - assertThat(registration.get()).isEqualTo(0); - } - - @Test - void registerWhenAlreadyRegisteredReplacesPreviousRegistration() { - Registration registration1 = this.registy.register(Integer.class, this.counter::getAndIncrement); - Registration 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 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 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 registration = this.registy.register(Integer.class, this.counter::getAndIncrement); - assertThat(registration.get()).isEqualTo(0); - } - - @Test - void registrationGetWhenCalledMultipleTimesReturnsSingleInstance() { - Registration 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 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 registration = this.registy.register(Integer.class, this.counter::getAndIncrement); - registration.onApplicationContextPrepared(null); - this.registy.applicationContextPrepared(this.context); - } - - private static class TestApplicationPreparedAction implements AssertProvider { - - 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 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; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java index 8655c5ff89d..82147c55878 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListenerTests.java @@ -20,6 +20,8 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent; @@ -45,23 +47,22 @@ class EnvironmentPostProcessorApplicationListenerTests { private DeferredLogs deferredLogs = spy(new DeferredLogs()); - private DefaultBootstrapRegisty bootstrapRegistry = spy(new DefaultBootstrapRegisty()); + private DefaultBootstrapContext bootstrapContext = spy(new DefaultBootstrapContext()); private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( - EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs, - this.bootstrapRegistry); + EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs); @Test void createUsesSpringFactories() { EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); - assertThat(listener.getEnvironmentPostProcessors()).hasSizeGreaterThan(1); + assertThat(listener.getEnvironmentPostProcessors(this.bootstrapContext)).hasSizeGreaterThan(1); } @Test void createWhenHasFactoryUsesFactory() { EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); - List postProcessors = listener.getEnvironmentPostProcessors(); + List postProcessors = listener.getEnvironmentPostProcessors(this.bootstrapContext); assertThat(postProcessors).hasSize(1); assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); } @@ -90,8 +91,8 @@ class EnvironmentPostProcessorApplicationListenerTests { void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() { SpringApplication application = mock(SpringApplication.class); MockEnvironment environment = new MockEnvironment(); - ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(application, new String[0], - environment); + ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(this.bootstrapContext, + application, new String[0], environment); this.listener.onApplicationEvent(event); assertThat(environment.getProperty("processed")).isEqualTo("true"); } @@ -105,15 +106,6 @@ class EnvironmentPostProcessorApplicationListenerTests { verify(this.deferredLogs).switchOverAll(); } - @Test - void onApplicationEventWhenApplicationPreparedEventTriggersRegistryActions() { - SpringApplication application = mock(SpringApplication.class); - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context); - this.listener.onApplicationEvent(event); - verify(this.bootstrapRegistry).applicationContextPrepared(context); - } - @Test void onApplicationEventWhenApplicationFailedEventSwitchesLogs() { SpringApplication application = mock(SpringApplication.class); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java index 212ac2c1a94..fd5e6cd5b85 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java @@ -21,6 +21,7 @@ import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; @@ -36,13 +37,13 @@ class EnvironmentPostProcessorsFactoryTests { private final DeferredLogFactory logFactory = Supplier::get; - private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); @Test void fromSpringFactoriesReturnsFactory() { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null); List processors = factory.getEnvironmentPostProcessors(this.logFactory, - this.bootstrapRegistry); + this.bootstrapContext); assertThat(processors).hasSizeGreaterThan(1); } @@ -51,7 +52,7 @@ class EnvironmentPostProcessorsFactoryTests { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory .of(TestEnvironmentPostProcessor.class); List processors = factory.getEnvironmentPostProcessors(this.logFactory, - this.bootstrapRegistry); + this.bootstrapContext); assertThat(processors).hasSize(1); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); } @@ -61,7 +62,7 @@ class EnvironmentPostProcessorsFactoryTests { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory .of(TestEnvironmentPostProcessor.class.getName()); List processors = factory.getEnvironmentPostProcessors(this.logFactory, - this.bootstrapRegistry); + this.bootstrapContext); assertThat(processors).hasSize(1); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java index 510c9be2cb9..568ddf59a1b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java @@ -24,6 +24,8 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.junit.jupiter.api.Test; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; @@ -40,7 +42,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { private final DeferredLogFactory logFactory = Supplier::get; - private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private final DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); @Test void createWithClassesCreatesFactory() { @@ -96,7 +98,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( BadEnvironmentPostProcessor.class.getName()); assertThatIllegalArgumentException() - .isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapRegistry)) + .isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapContext)) .withMessageContaining("Unable to instantiate"); } @@ -115,7 +117,7 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { void createsSinglePostProcessor(Class expectedType) { List processors = this.factory.getEnvironmentPostProcessors( ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory, - ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapRegistry); + ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapContext); assertThat(processors).hasSize(1); assertThat(processors.get(0)).isInstanceOf(expectedType); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/build.gradle new file mode 100644 index 00000000000..ad4cb96f738 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/build.gradle @@ -0,0 +1,12 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Bootstrap Registry smoke test" + +dependencies { + implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + + testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/MySubversionClient.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/MySubversionClient.java new file mode 100644 index 00000000000..58ba2801ccf --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/MySubversionClient.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.app; + +import smoketest.bootstrapregistry.external.svn.SubversionClient; +import smoketest.bootstrapregistry.external.svn.SubversionServerCertificate; + +public class MySubversionClient extends SubversionClient { + + public MySubversionClient(SubversionServerCertificate serverCertificate) { + super(serverCertificate); + } + + @Override + public String load(String location) { + return "my-" + super.load(location); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/Printer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/Printer.java new file mode 100644 index 00000000000..d9f7d1c7595 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/Printer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.app; + +import smoketest.bootstrapregistry.external.svn.SubversionClient; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class Printer { + + Printer(@Value("${svn}") String svn, SubversionClient subversionClient) { + System.out.println("--- svn " + svn); + System.out.println("--- client " + subversionClient.getClass().getName()); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplication.java new file mode 100644 index 00000000000..0b41f26005e --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplication.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.app; + +import smoketest.bootstrapregistry.external.svn.SubversionBootstrap; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleBootstrapRegistryApplication { + + public static void main(String[] args) { + // This example shows how a Bootstrapper can be used to register a custom + // SubversionClient that still has access to data provided in the + // application.properties file + SpringApplication application = new SpringApplication(SampleBootstrapRegistryApplication.class); + application.addBootstrapper(SubversionBootstrap.withCustomClient(MySubversionClient::new)); + application.run(args); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionBootstrap.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionBootstrap.java new file mode 100644 index 00000000000..8617c36fe5b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionBootstrap.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +import java.util.function.Function; + +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.Bootstrapper; + +/** + * Allows the user to register a {@link Bootstrapper} with a custom + * {@link SubversionClient}. + * + * @author Phillip Webb + */ +public final class SubversionBootstrap { + + private SubversionBootstrap() { + } + + /** + * Return a {@link Bootstrapper} for the given client factory. + * @param clientFactory the client factory + * @return a {@link Bootstrapper} instance + */ + public static Bootstrapper withCustomClient(Function clientFactory) { + return (registry) -> registry.register(SubversionClient.class, + (bootstrapContext) -> createSubversionClient(bootstrapContext, clientFactory)); + } + + private static SubversionClient createSubversionClient(BootstrapContext bootstrapContext, + Function clientFactory) { + return clientFactory.apply(bootstrapContext.get(SubversionServerCertificate.class)); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionClient.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionClient.java new file mode 100644 index 00000000000..663d65d87ab --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionClient.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +/** + * A client that can connect to a subversion server. + * + * @author Phillip Webb + */ +public class SubversionClient { + + private SubversionServerCertificate serverCertificate; + + public SubversionClient(SubversionServerCertificate serverCertificate) { + this.serverCertificate = serverCertificate; + } + + public String load(String location) { + return "data from svn / " + location + "[" + this.serverCertificate + "]"; + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java new file mode 100644 index 00000000000..6ef1bca6235 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +import java.io.IOException; +import java.util.Collections; + +import org.springframework.boot.BootstrapContext; +import org.springframework.boot.BootstrapContextClosedEvent; +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigDataLoader; +import org.springframework.boot.context.config.ConfigDataLoaderContext; +import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; + +/** + * {@link ConfigDataLoader} for subversion. + * + * @author Phillip Webb + */ +class SubversionConfigDataLoader implements ConfigDataLoader { + + private static final ApplicationListener 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)); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java new file mode 100644 index 00000000000..d812af0ac3a --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +import org.springframework.boot.context.config.ConfigDataLocation; + +/** + * A subversion {@link ConfigDataLocation}. + * + * @author Phillip Webb + */ +class SubversionConfigDataLocation extends ConfigDataLocation { + + private final String location; + + private final SubversionServerCertificate serverCertificate; + + SubversionConfigDataLocation(String location, String serverCertificate) { + this.location = location; + this.serverCertificate = SubversionServerCertificate.of(serverCertificate); + } + + String getLocation() { + return this.location; + } + + SubversionServerCertificate getServerCertificate() { + return this.serverCertificate; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SubversionConfigDataLocation other = (SubversionConfigDataLocation) obj; + return this.location.equals(other.location); + } + + @Override + public int hashCode() { + return this.location.hashCode(); + } + + @Override + public String toString() { + return this.location; + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java new file mode 100644 index 00000000000..74ea940a352 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; +import org.springframework.boot.context.config.ConfigDataLocationResolver; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; + +/** + * {@link ConfigDataLocationResolver} for subversion. + * + * @author Phillip Webb + */ +class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver { + + private static final String PREFIX = "svn:"; + + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { + return location.startsWith(PREFIX); + } + + @Override + public List 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)); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionServerCertificate.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionServerCertificate.java new file mode 100644 index 00000000000..ffb9ff3d146 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionServerCertificate.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.external.svn; + +import org.springframework.util.StringUtils; + +/** + * A certificate that can be used to provide a secure connection to the subversion server. + * + * @author Phillip Webb + */ +public class SubversionServerCertificate { + + private final String data; + + SubversionServerCertificate(String data) { + this.data = data; + } + + @Override + public String toString() { + return this.data; + } + + public static SubversionServerCertificate of(String data) { + return StringUtils.hasText(data) ? new SubversionServerCertificate(data) : null; + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/package-info.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/package-info.java new file mode 100644 index 00000000000..d6c317d7c80 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An example of a hypothetical library that supports subversion. + */ +package smoketest.bootstrapregistry.external.svn; diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/META-INF/spring.factories b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..2bc21d1989e --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +smoketest.bootstrapregistry.external.svn.SubversionConfigDataLocationResolver + +org.springframework.boot.context.config.ConfigDataLoader=\ +smoketest.bootstrapregistry.external.svn.SubversionConfigDataLoader diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/application.properties new file mode 100644 index 00000000000..d7f98258df5 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.svn.server.certificate=secret +spring.config.import=svn:example.com diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/test/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/test/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplicationTests.java new file mode 100644 index 00000000000..3dd99987ce4 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/test/java/smoketest/bootstrapregistry/app/SampleBootstrapRegistryApplicationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smoketest.bootstrapregistry.app; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SampleBootstrapRegistryApplication}. + * + * @author Phillip Webb + */ +@ExtendWith(OutputCaptureExtension.class) +class SampleBootstrapRegistryApplicationTests { + + @Test + void testBootrapper(CapturedOutput output) { + SampleBootstrapRegistryApplication.main(new String[0]); + assertThat(output).contains("svn my-data from svn / example.com[secret]") + .contains("client smoketest.bootstrapregistry.app.MySubversionClient"); + } + +}