Add BootstrapRegistry for long lived instances

Add a simple `BootstrapRegistry` that can be used to store and share
object instances across `EnvironmentPostProcessors`. The registry
can be injected into the constructor of any `EnvironmentPostProcessor`.

Registrations can also perform additional actions when the
`ApplicationContext` has been prepared. For example, they could register
the the bootstrap instances as beans so that they become available to
the application.

See gh-22956
This commit is contained in:
Phillip Webb 2020-08-20 12:51:34 -07:00
parent 167e31d564
commit 2260657781
11 changed files with 542 additions and 85 deletions

View File

@ -74,7 +74,7 @@ public final class RemoteSpringApplication {
List<ApplicationListener<?>> listeners = new ArrayList<>(); List<ApplicationListener<?>> listeners = new ArrayList<>();
listeners.add(new AnsiOutputApplicationListener()); listeners.add(new AnsiOutputApplicationListener());
listeners.add(new EnvironmentPostProcessorApplicationListener( listeners.add(new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.singleton(ConfigDataEnvironmentPostProcessor::new))); EnvironmentPostProcessorsFactory.of(ConfigDataEnvironmentPostProcessor.class)));
listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new ClasspathLoggingApplicationListener());
listeners.add(new LoggingApplicationListener()); listeners.add(new LoggingApplicationListener());
listeners.add(new RemoteUrlPropertyExtractor()); listeners.add(new RemoteUrlPropertyExtractor());

View File

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

View File

@ -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<Class<?>, DefaultRegistration<?>> registrations = new HashMap<>();
@Override
public <T> T get(Class<T> type, Supplier<T> instanceSupplier) {
return get(type, instanceSupplier, null);
}
@Override
public <T> T get(Class<T> type, Supplier<T> instanceSupplier,
BiConsumer<ConfigurableApplicationContext, T> onApplicationContextPreparedAction) {
Registration<T> registration = getRegistration(type);
if (registration != null) {
return registration.get();
}
registration = register(type, instanceSupplier);
registration.onApplicationContextPrepared(onApplicationContextPreparedAction);
return registration.get();
}
@Override
public <T> Registration<T> register(Class<T> type, Supplier<T> instanceSupplier) {
DefaultRegistration<T> registration = new DefaultRegistration<>(instanceSupplier);
this.registrations.put(type, registration);
return registration;
}
@Override
public <T> boolean isRegistered(Class<T> type) {
return getRegistration(type) != null;
}
@Override
@SuppressWarnings("unchecked")
public <T> Registration<T> getRegistration(Class<T> type) {
return (Registration<T>) this.registrations.get(type);
}
/**
* Method to be called when the {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
public void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
this.registrations.values()
.forEach((registration) -> registration.applicationContextPrepared(applicationContext));
}
/**
* Default implementation of {@link Registration}.
*/
private static class DefaultRegistration<T> implements Registration<T> {
private Supplier<T> instanceSupplier;
private volatile T instance;
private List<BiConsumer<ConfigurableApplicationContext, T>> applicationContextPreparedActions = new ArrayList<>();
DefaultRegistration(Supplier<T> instanceSupplier) {
this.instanceSupplier = instanceSupplier;
}
@Override
public T get() {
T instance = this.instance;
if (instance == null) {
synchronized (this.instanceSupplier) {
instance = this.instanceSupplier.get();
this.instance = instance;
}
}
return instance;
}
@Override
public void onApplicationContextPrepared(BiConsumer<ConfigurableApplicationContext, T> action) {
if (action != null) {
this.applicationContextPreparedActions.add(action);
}
}
/**
* Method called when the {@link ApplicationContext} is prepared.
* @param applicationContext the prepared context
*/
void applicationContextPrepared(ConfigurableApplicationContext applicationContext) {
this.applicationContextPreparedActions.forEach((consumer) -> consumer.accept(applicationContext, get()));
}
}
}

View File

@ -34,9 +34,16 @@ import org.springframework.core.env.Environment;
* if they wish to be invoked in specific order. * if they wish to be invoked in specific order.
* <p> * <p>
* Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally * Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally
* take a single {@link Log} or {@link DeferredLogFactory} instance as a constructor * take the following constructor parameters:
* argument. The injected {@link Log} instance will defer output until the application has * <ul>
* been full prepared to allow the environment itself to configure logging levels. * <li>{@link DeferredLogFactory} - A factory that can be used to create loggers with
* output deferred until the application has been full prepared (allowing the environment
* itself to configure logging levels).</li>
* <li>{@link Log} - A log with output deferred until the application has been full
* prepared (allowing the environment itself to configure logging levels).</li>
* <li>{@link BootstrapRegistry} - A bootstrap registry that can be used to store objects
* that may be expensive to create, or need to be shared.</li>
* </ul>
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll

View File

@ -44,6 +44,8 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
private final DeferredLogs deferredLogs; private final DeferredLogs deferredLogs;
private final DefaultBootstrapRegisty bootstrapRegistry;
private int order = DEFAULT_ORDER; private int order = DEFAULT_ORDER;
private final EnvironmentPostProcessorsFactory postProcessorsFactory; private final EnvironmentPostProcessorsFactory postProcessorsFactory;
@ -63,13 +65,14 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
* @param postProcessorsFactory the post processors factory * @param postProcessorsFactory the post processors factory
*/ */
public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) { public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) {
this(postProcessorsFactory, new DeferredLogs()); this(postProcessorsFactory, new DeferredLogs(), new DefaultBootstrapRegisty());
} }
EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory, EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory,
DeferredLogs deferredLogs) { DeferredLogs deferredLogs, DefaultBootstrapRegisty bootstrapRegistry) {
this.postProcessorsFactory = postProcessorsFactory; this.postProcessorsFactory = postProcessorsFactory;
this.deferredLogs = deferredLogs; this.deferredLogs = deferredLogs;
this.bootstrapRegistry = bootstrapRegistry;
} }
@Override @Override
@ -84,8 +87,11 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
if (event instanceof ApplicationEnvironmentPreparedEvent) { if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
} }
if (event instanceof ApplicationPreparedEvent || event instanceof ApplicationFailedEvent) { if (event instanceof ApplicationPreparedEvent) {
onFinish(); onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
} }
} }
@ -97,14 +103,19 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica
} }
} }
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() { private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs); this.deferredLogs.switchOverAll();
this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext());
} }
private void onFinish() { private void onApplicationFailedEvent(ApplicationFailedEvent event) {
this.deferredLogs.switchOverAll(); this.deferredLogs.switchOverAll();
} }
List<EnvironmentPostProcessor> getEnvironmentPostProcessors() {
return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, this.bootstrapRegistry);
}
@Override @Override
public int getOrder() { public int getOrder() {
return this.order; return this.order;

View File

@ -16,9 +16,7 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
@ -36,9 +34,11 @@ public interface EnvironmentPostProcessorsFactory {
/** /**
* Create all requested {@link EnvironmentPostProcessor} instances. * Create all requested {@link EnvironmentPostProcessor} instances.
* @param logFactory a deferred log factory * @param logFactory a deferred log factory
* @param bootstrapRegistry a bootstrap registry
* @return the post processor instances * @return the post processor instances
*/ */
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory); List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
BootstrapRegistry bootstrapRegistry);
/** /**
* Return a {@link EnvironmentPostProcessorsFactory} backed by * Return a {@link EnvironmentPostProcessorsFactory} backed by
@ -71,14 +71,4 @@ public interface EnvironmentPostProcessorsFactory {
return new ReflectionEnvironmentPostProcessorsFactory(classNames); 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<DeferredLogFactory, EnvironmentPostProcessor> factory) {
return (logFactory) -> Collections.singletonList(factory.apply(logFactory));
}
} }

View File

@ -16,18 +16,13 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.boot.util.Instantiator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* {@link EnvironmentPostProcessorsFactory} implementation that uses reflection to create * {@link EnvironmentPostProcessorsFactory} implementation that uses reflection to create
@ -52,43 +47,15 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce
} }
@Override @Override
public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory) { public List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
List<EnvironmentPostProcessor> postProcessors = new ArrayList<>(this.classNames.size()); BootstrapRegistry bootstrapRegistry) {
for (String className : this.classNames) { Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,
try { (parameters) -> {
postProcessors.add(getEnvironmentPostProcessor(className, logFactory)); parameters.add(DeferredLogFactory.class, logFactory);
} parameters.add(Log.class, logFactory::getLog);
catch (Throwable ex) { parameters.add(BootstrapRegistry.class, bootstrapRegistry);
throw new IllegalArgumentException("Unable to instantiate factory class [" + className });
+ "] for factory type [" + EnvironmentPostProcessor.class.getName() + "]", ex); return instantiator.instantiate(this.classNames);
}
}
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);
} }
} }

View File

@ -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<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registerWhenAlreadyRegisteredReplacesPreviousRegistration() {
Registration<Integer> registration1 = this.registy.register(Integer.class, this.counter::getAndIncrement);
Registration<Integer> registration2 = this.registy.register(Integer.class, () -> -1);
assertThat(registration2).isNotEqualTo(registration1);
assertThat(registration1.get()).isEqualTo(0);
assertThat(registration2.get()).isEqualTo(-1);
assertThat(this.registy.get(Integer.class, this.counter::getAndIncrement)).isEqualTo(-1);
}
@Test
void isRegisteredWhenNotRegisteredReturnsFalse() {
assertThat(this.registy.isRegistered(Integer.class)).isFalse();
}
@Test
void isRegisteredWhenRegisteredReturnsTrue() {
this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(this.registy.isRegistered(Integer.class)).isTrue();
}
@Test
void getRegistrationWhenNotRegisteredReturnsNull() {
assertThat(this.registy.getRegistration(Integer.class)).isNull();
}
@Test
void getRegistrationWhenRegisteredReturnsRegistration() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(this.registy.getRegistration(Integer.class)).isSameAs(registration);
}
@Test
void applicationContextPreparedTriggersActions() {
TestApplicationPreparedAction action1 = new TestApplicationPreparedAction();
TestApplicationPreparedAction action2 = new TestApplicationPreparedAction();
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(action1::run);
registration.onApplicationContextPrepared(action2::run);
this.registy.applicationContextPrepared(this.context);
assertThat(action1).wasCalledOnlyOnce().hasInstanceValue(0);
assertThat(action2).wasCalledOnlyOnce().hasInstanceValue(0);
}
@Test
void registrationGetReturnsInstance() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registrationGetWhenCalledMultipleTimesReturnsSingleInstance() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
assertThat(registration.get()).isEqualTo(0);
assertThat(registration.get()).isEqualTo(0);
assertThat(registration.get()).isEqualTo(0);
}
@Test
void registrationOnApplicationContextPreparedAddsAction() {
TestApplicationPreparedAction action = new TestApplicationPreparedAction();
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(action::run);
this.registy.applicationContextPrepared(this.context);
assertThat(action).wasCalledOnlyOnce().hasInstanceValue(0);
}
@Test
void registrationOnApplicationContextPreparedWhenActionIsNullDoesNotAddAction() {
Registration<Integer> registration = this.registy.register(Integer.class, this.counter::getAndIncrement);
registration.onApplicationContextPrepared(null);
this.registy.applicationContextPrepared(this.context);
}
private static class TestApplicationPreparedAction implements AssertProvider<ApplicationPreparedActionAssert> {
private Integer instance;
private int called;
void run(ConfigurableApplicationContext context, Integer instance) {
this.instance = instance;
this.called++;
}
@Override
public ApplicationPreparedActionAssert assertThat() {
return new ApplicationPreparedActionAssert(this);
}
}
private static class ApplicationPreparedActionAssert
extends AbstractAssert<ApplicationPreparedActionAssert, TestApplicationPreparedAction> {
ApplicationPreparedActionAssert(TestApplicationPreparedAction actual) {
super(actual, ApplicationPreparedActionAssert.class);
}
ApplicationPreparedActionAssert hasInstanceValue(Integer expected) {
assertThat(this.actual.instance).isEqualTo(expected);
return this;
}
ApplicationPreparedActionAssert wasCalledOnlyOnce() {
assertThat(this.actual.called).as("action calls").isEqualTo(1);
return this;
}
ApplicationPreparedActionAssert wasNotCalled() {
assertThat(this.actual.called).as("action calls").isEqualTo(0);
return this;
}
}
}

View File

@ -45,8 +45,11 @@ class EnvironmentPostProcessorApplicationListenerTests {
private DeferredLogs deferredLogs = spy(new DeferredLogs()); private DeferredLogs deferredLogs = spy(new DeferredLogs());
private DefaultBootstrapRegisty bootstrapRegistry = spy(new DefaultBootstrapRegisty());
private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new), this.deferredLogs); EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class), this.deferredLogs,
this.bootstrapRegistry);
@Test @Test
void createUsesSpringFactories() { void createUsesSpringFactories() {
@ -57,7 +60,7 @@ class EnvironmentPostProcessorApplicationListenerTests {
@Test @Test
void createWhenHasFactoryUsesFactory() { void createWhenHasFactoryUsesFactory() {
EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(
EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new)); EnvironmentPostProcessorsFactory.of(TestEnvironmentPostProcessor.class));
List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors(); List<EnvironmentPostProcessor> postProcessors = listener.getEnvironmentPostProcessors();
assertThat(postProcessors).hasSize(1); assertThat(postProcessors).hasSize(1);
assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
@ -102,6 +105,15 @@ class EnvironmentPostProcessorApplicationListenerTests {
verify(this.deferredLogs).switchOverAll(); verify(this.deferredLogs).switchOverAll();
} }
@Test
void onApplicationEventWhenApplicationPreparedEventTriggersRegistryActions() {
SpringApplication application = mock(SpringApplication.class);
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
ApplicationPreparedEvent event = new ApplicationPreparedEvent(application, new String[0], context);
this.listener.onApplicationEvent(event);
verify(this.bootstrapRegistry).applicationContextPrepared(context);
}
@Test @Test
void onApplicationEventWhenApplicationFailedEventSwitchesLogs() { void onApplicationEventWhenApplicationFailedEventSwitchesLogs() {
SpringApplication application = mock(SpringApplication.class); SpringApplication application = mock(SpringApplication.class);
@ -114,7 +126,9 @@ class EnvironmentPostProcessorApplicationListenerTests {
static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
TestEnvironmentPostProcessor(DeferredLogFactory logFactory) { TestEnvironmentPostProcessor(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry) {
assertThat(logFactory).isNotNull();
assertThat(bootstrapRegistry).isNotNull();
} }
@Override @Override

View File

@ -36,10 +36,13 @@ class EnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get; private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
@Test @Test
void fromSpringFactoriesReturnsFactory() { void fromSpringFactoriesReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null); EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory); List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry);
assertThat(processors).hasSizeGreaterThan(1); assertThat(processors).hasSizeGreaterThan(1);
} }
@ -47,7 +50,8 @@ class EnvironmentPostProcessorsFactoryTests {
void ofClassesReturnsFactory() { void ofClassesReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class); .of(TestEnvironmentPostProcessor.class);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory); List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
this.bootstrapRegistry);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
} }
@ -56,16 +60,8 @@ class EnvironmentPostProcessorsFactoryTests {
void ofClassNamesReturnsFactory() { void ofClassNamesReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.of(TestEnvironmentPostProcessor.class.getName()); .of(TestEnvironmentPostProcessor.class.getName());
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory); List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory,
assertThat(processors).hasSize(1); this.bootstrapRegistry);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
}
@Test
void singletonReturnsFactory() {
EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory
.singleton(TestEnvironmentPostProcessor::new);
List<EnvironmentPostProcessor> processors = factory.getEnvironmentPostProcessors(this.logFactory);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class);
} }

View File

@ -40,6 +40,8 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
private final DeferredLogFactory logFactory = Supplier::get; private final DeferredLogFactory logFactory = Supplier::get;
private final BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty();
@Test @Test
void createWithClassesCreatesFactory() { void createWithClassesCreatesFactory() {
ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
@ -82,11 +84,19 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class); assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class);
} }
@Test
void getEnvironmentPostProcessorsWhenHasBootstrapRegistryConstructorCreatesPostProcessors() {
ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
TestBootstrapRegistryEnvironmentPostProcessor.class.getName());
assertThatFactory(factory).createsSinglePostProcessor(TestBootstrapRegistryEnvironmentPostProcessor.class);
}
@Test @Test
void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() { void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() {
ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(
BadEnvironmentPostProcessor.class.getName()); BadEnvironmentPostProcessor.class.getName());
assertThatIllegalArgumentException().isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory)) assertThatIllegalArgumentException()
.isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapRegistry))
.withMessageContaining("Unable to instantiate"); .withMessageContaining("Unable to instantiate");
} }
@ -103,8 +113,9 @@ class ReflectionEnvironmentPostProcessorsFactoryTests {
} }
void createsSinglePostProcessor(Class<?> expectedType) { void createsSinglePostProcessor(Class<?> expectedType) {
List<EnvironmentPostProcessor> processors = this.factory List<EnvironmentPostProcessor> processors = this.factory.getEnvironmentPostProcessors(
.getEnvironmentPostProcessors(ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory); ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory,
ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapRegistry);
assertThat(processors).hasSize(1); assertThat(processors).hasSize(1);
assertThat(processors.get(0)).isInstanceOf(expectedType); 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 { static class BadEnvironmentPostProcessor implements EnvironmentPostProcessor {
BadEnvironmentPostProcessor(InputStream inputStream) { BadEnvironmentPostProcessor(InputStream inputStream) {