diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java index 3bf0a3f17c9..4af5df413d2 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/RemoteSpringApplication.java @@ -74,7 +74,7 @@ public final class RemoteSpringApplication { List> listeners = new ArrayList<>(); listeners.add(new AnsiOutputApplicationListener()); listeners.add(new EnvironmentPostProcessorApplicationListener( - EnvironmentPostProcessorsFactory.singleton(ConfigDataEnvironmentPostProcessor::new))); + EnvironmentPostProcessorsFactory.of(ConfigDataEnvironmentPostProcessor.class))); listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new LoggingApplicationListener()); listeners.add(new RemoteUrlPropertyExtractor()); 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 new file mode 100644 index 00000000000..030c1ee56de --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/BootstrapRegistry.java @@ -0,0 +1,123 @@ +/* + * 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 new file mode 100644 index 00000000000..a64c39cafb3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java @@ -0,0 +1,127 @@ +/* + * 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)); + } + + /** + * 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 b55aa37ef9e..a9f6b47af5f 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 @@ -34,9 +34,16 @@ import org.springframework.core.env.Environment; * if they wish to be invoked in specific order. *

* Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally - * take a single {@link Log} or {@link DeferredLogFactory} instance as a constructor - * argument. The injected {@link Log} instance will defer output until the application has - * been full prepared to allow the environment itself to configure logging levels. + * take the following constructor parameters: + *

* * @author Andy Wilkinson * @author Stephane Nicoll 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 74b18bf05bc..b387cb67cc2 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 @@ -44,6 +44,8 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica private final DeferredLogs deferredLogs; + private final DefaultBootstrapRegisty bootstrapRegistry; + private int order = DEFAULT_ORDER; private final EnvironmentPostProcessorsFactory postProcessorsFactory; @@ -63,13 +65,14 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica * @param postProcessorsFactory the post processors factory */ public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) { - this(postProcessorsFactory, new DeferredLogs()); + this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty()); } EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory, - DeferredLogs deferredLogs) { + DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) { this.postProcessorsFactory = postProcessorsFactory; this.deferredLogs = deferredLogs; + this.bootstrapRegistry = bootstrapRegistry; } @Override @@ -84,8 +87,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } - if (event instanceof ApplicationPreparedEvent || event instanceof ApplicationFailedEvent) { - onFinish(); + if (event instanceof ApplicationPreparedEvent) { + onApplicationPreparedEvent((ApplicationPreparedEvent) event); + } + if (event instanceof ApplicationFailedEvent) { + onApplicationFailedEvent((ApplicationFailedEvent) event); } } @@ -97,14 +103,19 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica } } - List getEnvironmentPostProcessors() { - return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs); + private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { + this.deferredLogs.switchOverAll(); + this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext()); } - private void onFinish() { + private void onApplicationFailedEvent(ApplicationFailedEvent event) { this.deferredLogs.switchOverAll(); } + List getEnvironmentPostProcessors() { + return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry); + } + @Override public int getOrder() { return this.order; 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 e6b7faa1522..1074ccace03 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 @@ -16,9 +16,7 @@ package org.springframework.boot.env; -import java.util.Collections; import java.util.List; -import java.util.function.Function; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -36,9 +34,11 @@ public interface EnvironmentPostProcessorsFactory { /** * Create all requested {@link EnvironmentPostProcessor} instances. * @param logFactory a deferred log factory + * @param bootstrapRegistry a bootstrap registry * @return the post processor instances */ - List getEnvironmentPostProcessors(DeferredLogFactory logFactory); + List getEnvironmentPostProcessors(DeferredLogFactory logFactory, + BootstrapRegistry bootstrapRegistry); /** * Return a {@link EnvironmentPostProcessorsFactory} backed by @@ -71,14 +71,4 @@ public interface EnvironmentPostProcessorsFactory { return new ReflectionEnvironmentPostProcessorsFactory(classNames); } - /** - * Create a {@link EnvironmentPostProcessorsFactory} containing only a single post - * processor. - * @param factory the factory used to create the post processor - * @return an {@link EnvironmentPostProcessorsFactory} instance - */ - static EnvironmentPostProcessorsFactory singleton(Function factory) { - return (logFactory) -> Collections.singletonList(factory.apply(logFactory)); - } - } 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 81db657ce4e..baab7439ddb 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 @@ -16,18 +16,13 @@ package org.springframework.boot.env; -import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; import org.springframework.boot.logging.DeferredLogFactory; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; +import org.springframework.boot.util.Instantiator; /** * {@link EnvironmentPostProcessorsFactory} implementation that uses reflection to create @@ -52,43 +47,15 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce } @Override - public List getEnvironmentPostProcessors(DeferredLogFactory logFactory) { - List postProcessors = new ArrayList<>(this.classNames.size()); - for (String className : this.classNames) { - try { - postProcessors.add(getEnvironmentPostProcessor(className, logFactory)); - } - catch (Throwable ex) { - throw new IllegalArgumentException("Unable to instantiate factory class [" + className - + "] for factory type [" + EnvironmentPostProcessor.class.getName() + "]", ex); - } - } - AnnotationAwareOrderComparator.sort(postProcessors); - return postProcessors; - } - - private EnvironmentPostProcessor getEnvironmentPostProcessor(String className, DeferredLogFactory logFactory) - throws Exception { - Class type = ClassUtils.forName(className, getClass().getClassLoader()); - Assert.isAssignable(EnvironmentPostProcessor.class, type); - Constructor[] constructors = type.getDeclaredConstructors(); - for (Constructor constructor : constructors) { - if (constructor.getParameterCount() == 1) { - Class cls = constructor.getParameterTypes()[0]; - if (DeferredLogFactory.class.isAssignableFrom(cls)) { - return newInstance(constructor, logFactory); - } - if (Log.class.isAssignableFrom(cls)) { - return newInstance(constructor, logFactory.getLog(type)); - } - } - } - return (EnvironmentPostProcessor) ReflectionUtils.accessibleConstructor(type).newInstance(); - } - - private EnvironmentPostProcessor newInstance(Constructor constructor, Object... initargs) throws Exception { - ReflectionUtils.makeAccessible(constructor); - return (EnvironmentPostProcessor) constructor.newInstance(initargs); + public List getEnvironmentPostProcessors(DeferredLogFactory logFactory, + BootstrapRegistry bootstrapRegistry) { + Instantiator instantiator = new Instantiator<>(EnvironmentPostProcessor.class, + (parameters) -> { + parameters.add(DeferredLogFactory.class, logFactory); + parameters.add(Log.class, logFactory::getLog); + parameters.add(BootstrapRegistry.class, bootstrapRegistry); + }); + return instantiator.instantiate(this.classNames); } } 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 new file mode 100644 index 00000000000..b4faf99c12a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/DefaultBootstrapRegistyTests.java @@ -0,0 +1,199 @@ +/* + * 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 332fdb70749..8655c5ff89d 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 @@ -45,8 +45,11 @@ class EnvironmentPostProcessorApplicationListenerTests { private DeferredLogs deferredLogs = spy(new DeferredLogs()); + private DefaultBootstrapRegisty bootstrapRegistry = spy(new DefaultBootstrapRegisty()); + private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( - EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new), this.deferredLogs); + EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs, + this.bootstrapRegistry); @Test void createUsesSpringFactories() { @@ -57,7 +60,7 @@ class EnvironmentPostProcessorApplicationListenerTests { @Test void createWhenHasFactoryUsesFactory() { EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( - EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new)); + EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class)); List postProcessors = listener.getEnvironmentPostProcessors(); assertThat(postProcessors).hasSize(1); assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); @@ -102,6 +105,15 @@ 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); @@ -114,7 +126,9 @@ class EnvironmentPostProcessorApplicationListenerTests { static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { - TestEnvironmentPostProcessor(DeferredLogFactory logFactory) { + TestEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) { + assertThat(logFactory).isNotNull(); + assertThat(bootstrapRegistry).isNotNull(); } @Override 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 2624d646814..212ac2c1a94 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 @@ -36,10 +36,13 @@ class EnvironmentPostProcessorsFactoryTests { private final DeferredLogFactory logFactory = Supplier::get; + private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + @Test void fromSpringFactoriesReturnsFactory() { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null); - List processors = factory.getEnvironmentPostProcessors(this.logFactory); + List processors = factory.getEnvironmentPostProcessors(this.logFactory, + this.bootstrapRegistry); assertThat(processors).hasSizeGreaterThan(1); } @@ -47,7 +50,8 @@ class EnvironmentPostProcessorsFactoryTests { void ofClassesReturnsFactory() { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory .of(TestEnvironmentPostProcessor.class); - List processors = factory.getEnvironmentPostProcessors(this.logFactory); + List processors = factory.getEnvironmentPostProcessors(this.logFactory, + this.bootstrapRegistry); assertThat(processors).hasSize(1); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); } @@ -56,16 +60,8 @@ class EnvironmentPostProcessorsFactoryTests { void ofClassNamesReturnsFactory() { EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory .of(TestEnvironmentPostProcessor.class.getName()); - List processors = factory.getEnvironmentPostProcessors(this.logFactory); - assertThat(processors).hasSize(1); - assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); - } - - @Test - void singletonReturnsFactory() { - EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory - .singleton(TestEnvironmentPostProcessor::new); - List processors = factory.getEnvironmentPostProcessors(this.logFactory); + List processors = factory.getEnvironmentPostProcessors(this.logFactory, + this.bootstrapRegistry); 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 481b5ef02ca..510c9be2cb9 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 @@ -40,6 +40,8 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { private final DeferredLogFactory logFactory = Supplier::get; + private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + @Test void createWithClassesCreatesFactory() { ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( @@ -82,11 +84,19 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class); } + @Test + void getEnvironmentPostProcessorsWhenHasBootstrapRegistryConstructorCreatesPostProcessors() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestBootstrapRegistryEnvironmentPostProcessor.class.getName()); + assertThatFactory(factory).createsSinglePostProcessor(TestBootstrapRegistryEnvironmentPostProcessor.class); + } + @Test void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() { ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( BadEnvironmentPostProcessor.class.getName()); - assertThatIllegalArgumentException().isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory)) + assertThatIllegalArgumentException() + .isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapRegistry)) .withMessageContaining("Unable to instantiate"); } @@ -103,8 +113,9 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { } void createsSinglePostProcessor(Class expectedType) { - List processors = this.factory - .getEnvironmentPostProcessors(ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory); + List processors = this.factory.getEnvironmentPostProcessors( + ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory, + ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapRegistry); assertThat(processors).hasSize(1); assertThat(processors.get(0)).isInstanceOf(expectedType); } @@ -143,6 +154,18 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { } + static class TestBootstrapRegistryEnvironmentPostProcessor implements EnvironmentPostProcessor { + + TestBootstrapRegistryEnvironmentPostProcessor(BootstrapRegistry bootstrapRegistry) { + assertThat(bootstrapRegistry).isNotNull(); + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + static class BadEnvironmentPostProcessor implements EnvironmentPostProcessor { BadEnvironmentPostProcessor(InputStream inputStream) {