From 36a6ca6e6e63d16c3b5cfc163071d4bdaf14ea1f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 23 Jul 2020 11:40:49 -0700 Subject: [PATCH] Add EnvironmentPostProcessorsFactory Update `EnvironmentPostProcessorApplicationListener` so that it can either use values from `spring.factories` or use a factory interface. Closes gh-22529 --- .../devtools/RemoteSpringApplication.java | 4 +- ...nmentPostProcessorApplicationListener.java | 102 +++--------- .../env/EnvironmentPostProcessorsFactory.java | 84 ++++++++++ ...ctionEnvironmentPostProcessorsFactory.java | 94 +++++++++++ ...PostProcessorApplicationListenerTests.java | 98 ++++++++++- ...EnvironmentPostProcessorsFactoryTests.java | 84 ++++++++++ ...EnvironmentPostProcessorsFactoryTests.java | 157 ++++++++++++++++++ 7 files changed, 537 insertions(+), 86 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java 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 354e1de2c42..3bf0a3f17c9 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 @@ -33,6 +33,7 @@ import org.springframework.boot.devtools.restart.RestartInitializer; import org.springframework.boot.devtools.restart.RestartScopeInitializer; import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener; +import org.springframework.boot.env.EnvironmentPostProcessorsFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.core.io.ClassPathResource; @@ -72,7 +73,8 @@ public final class RemoteSpringApplication { private Collection> getListeners() { List> listeners = new ArrayList<>(); listeners.add(new AnsiOutputApplicationListener()); - listeners.add(new EnvironmentPostProcessorApplicationListener(ConfigDataEnvironmentPostProcessor.class)); + listeners.add(new EnvironmentPostProcessorApplicationListener( + EnvironmentPostProcessorsFactory.singleton(ConfigDataEnvironmentPostProcessor::new))); 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/EnvironmentPostProcessorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java index 12b9b59cf17..ecf17e31fe8 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 @@ -16,29 +16,17 @@ package org.springframework.boot.env; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; -import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogs; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.SmartApplicationListener; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; /** * {@link SmartApplicationListener} used to trigger {@link EnvironmentPostProcessor @@ -54,46 +42,34 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica */ public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; - private final DeferredLogs deferredLogs = new DeferredLogs(); + private final DeferredLogs deferredLogs; private int order = DEFAULT_ORDER; - private final List postProcessorClassNames; + private EnvironmentPostProcessorsFactory postProcessorsFactory; /** * Create a new {@link EnvironmentPostProcessorApplicationListener} with * {@link EnvironmentPostProcessor} classes loaded via {@code spring.factories}. */ public EnvironmentPostProcessorApplicationListener() { - this(SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, - EnvironmentPostProcessorApplicationListener.class.getClassLoader())); + this(EnvironmentPostProcessorsFactory + .fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader())); } /** - * Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified - * {@link EnvironmentPostProcessor} classes. - * @param postProcessorClasses the environment post processor classes + * Create a new {@link EnvironmentPostProcessorApplicationListener} with post + * processors created by the given factory. + * @param postProcessorsFactory the post processors factory */ - public EnvironmentPostProcessorApplicationListener(Class... postProcessorClasses) { - this(Arrays.stream(postProcessorClasses).map(Class::getName).collect(Collectors.toList())); + public EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory) { + this(postProcessorsFactory, new DeferredLogs()); } - /** - * Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified - * {@link EnvironmentPostProcessor} class names. - * @param postProcessorClassNames the environment post processor class names - */ - public EnvironmentPostProcessorApplicationListener(String... postProcessorClassNames) { - this(Arrays.asList(postProcessorClassNames)); - } - - /** - * Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified - * {@link EnvironmentPostProcessor} class names. - * @param postProcessorClassNames the environment post processor class names - */ - public EnvironmentPostProcessorApplicationListener(List postProcessorClassNames) { - this.postProcessorClassNames = postProcessorClassNames; + EnvironmentPostProcessorApplicationListener(EnvironmentPostProcessorsFactory postProcessorsFactory, + DeferredLogs deferredLogs) { + this.postProcessorsFactory = postProcessorsFactory; + this.deferredLogs = deferredLogs; } @Override @@ -108,64 +84,24 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } - if (event instanceof ApplicationPreparedEvent) { - onApplicationPreparedEvent((ApplicationPreparedEvent) event); + if (event instanceof ApplicationPreparedEvent || event instanceof ApplicationFailedEvent) { + onFinish(); } } private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); SpringApplication application = event.getSpringApplication(); - List postProcessors = loadPostProcessors(event.getSpringApplication()); - for (EnvironmentPostProcessor postProcessor : postProcessors) { + for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors()) { postProcessor.postProcessEnvironment(environment, application); } } - private List loadPostProcessors(SpringApplication application) { - return loadPostProcessors(application, this.postProcessorClassNames); + List getEnvironmentPostProcessors() { + return this.postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs); } - private List loadPostProcessors(SpringApplication application, List names) { - List postProcessors = new ArrayList<>(names.size()); - for (String name : names) { - try { - postProcessors.add(instantiatePostProcessor(application, name)); - } - catch (Throwable ex) { - throw new IllegalArgumentException("Unable to instantiate factory class [" + name - + "] for factory type [" + EnvironmentPostProcessor.class.getName() + "]", ex); - } - } - AnnotationAwareOrderComparator.sort(postProcessors); - return postProcessors; - } - - private EnvironmentPostProcessor instantiatePostProcessor(SpringApplication application, String name) - throws Exception { - Class type = ClassUtils.forName(name, 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, this.deferredLogs); - } - if (Log.class.isAssignableFrom(cls)) { - return newInstance(constructor, this.deferredLogs.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); - } - - private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { + private void onFinish() { this.deferredLogs.switchOverAll(); } 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 new file mode 100644 index 00000000000..e6b7faa1522 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java @@ -0,0 +1,84 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.function.Function; + +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * Factory interface used by the {@link EnvironmentPostProcessorApplicationListener} to + * create the {@link EnvironmentPostProcessor} instances. + * + * @author Phillip Webb + * @since 2.4.0 + */ +@FunctionalInterface +public interface EnvironmentPostProcessorsFactory { + + /** + * Create all requested {@link EnvironmentPostProcessor} instances. + * @param logFactory a deferred log factory + * @return the post processor instances + */ + List getEnvironmentPostProcessors(DeferredLogFactory logFactory); + + /** + * Return a {@link EnvironmentPostProcessorsFactory} backed by + * {@code spring.factories}. + * @param classLoader the source class loader + * @return an {@link EnvironmentPostProcessorsFactory} instance + */ + static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) { + return new ReflectionEnvironmentPostProcessorsFactory( + SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader)); + } + + /** + * Return a {@link EnvironmentPostProcessorsFactory} that reflectively creates post + * processors from the given classes. + * @param classes the post processor classes + * @return an {@link EnvironmentPostProcessorsFactory} instance + */ + static EnvironmentPostProcessorsFactory of(Class... classes) { + return new ReflectionEnvironmentPostProcessorsFactory(classes); + } + + /** + * Return a {@link EnvironmentPostProcessorsFactory} that reflectively creates post + * processors from the given class names. + * @param classNames the post processor class names + * @return an {@link EnvironmentPostProcessorsFactory} instance + */ + static EnvironmentPostProcessorsFactory of(String... 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 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 new file mode 100644 index 00000000000..81db657ce4e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java @@ -0,0 +1,94 @@ +/* + * 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.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; + +/** + * {@link EnvironmentPostProcessorsFactory} implementation that uses reflection to create + * instances. + * + * @author Phillip Webb + */ +class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProcessorsFactory { + + private final List classNames; + + ReflectionEnvironmentPostProcessorsFactory(Class... classes) { + this(Arrays.stream(classes).map(Class::getName).toArray(String[]::new)); + } + + ReflectionEnvironmentPostProcessorsFactory(String... classNames) { + this(Arrays.asList(classNames)); + } + + ReflectionEnvironmentPostProcessorsFactory(List classNames) { + this.classNames = classNames; + } + + @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); + } + +} 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 3eb617b1e78..f1db7e4d82b 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 @@ -16,8 +16,26 @@ package org.springframework.boot.env; +import java.util.List; + import org.junit.jupiter.api.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.boot.context.event.ApplicationPreparedEvent; +import org.springframework.boot.context.event.ApplicationStartingEvent; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.boot.logging.DeferredLogs; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + /** * Tests for {@link EnvironmentPostProcessorApplicationListener}. * @@ -25,9 +43,85 @@ import org.junit.jupiter.api.Test; */ class EnvironmentPostProcessorApplicationListenerTests { + private DeferredLogs deferredLogs = spy(new DeferredLogs()); + + private EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( + EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new), this.deferredLogs); + @Test - void test() { - // fail("Not yet implemented"); + void createUsesSpringFactories() { + EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener(); + assertThat(listener.getEnvironmentPostProcessors()).hasSizeGreaterThan(1); + } + + @Test + void createWhenHasFactoryUsesFactory() { + EnvironmentPostProcessorApplicationListener listener = new EnvironmentPostProcessorApplicationListener( + EnvironmentPostProcessorsFactory.singleton(TestEnvironmentPostProcessor::new)); + List postProcessors = listener.getEnvironmentPostProcessors(); + assertThat(postProcessors).hasSize(1); + assertThat(postProcessors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); + } + + @Test + void supporteEventTypeWhenApplicationEnvironmentPreparedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationEnvironmentPreparedEvent.class)).isTrue(); + } + + @Test + void supporteEventTypeWhenApplicationPreparedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationPreparedEvent.class)).isTrue(); + } + + @Test + void supporteEventTypeWhenApplicationFailedEventReturnsTrue() { + assertThat(this.listener.supportsEventType(ApplicationFailedEvent.class)).isTrue(); + } + + @Test + void supporteEventTypeWhenOtherEventReturnsFalse() { + assertThat(this.listener.supportsEventType(ApplicationStartingEvent.class)).isFalse(); + } + + @Test + void onApplicationEventWhenApplicationEnvironmentPreparedEventCallsPostProcessors() { + SpringApplication application = mock(SpringApplication.class); + MockEnvironment environment = new MockEnvironment(); + ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(application, new String[0], + environment); + this.listener.onApplicationEvent(event); + assertThat(environment.getProperty("processed")).isEqualTo("true"); + } + + @Test + void onApplicationEventWhenApplicationPreparedEventSwitchesLogs() { + 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.deferredLogs).switchOverAll(); + } + + @Test + void onApplicationEventWhenApplicationFailedEventSwitchesLogs() { + SpringApplication application = mock(SpringApplication.class); + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + ApplicationFailedEvent event = new ApplicationFailedEvent(application, new String[0], context, + new RuntimeException()); + this.listener.onApplicationEvent(event); + verify(this.deferredLogs).switchOverAll(); + } + + static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { + + TestEnvironmentPostProcessor(DeferredLogFactory logFactory) { + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + ((MockEnvironment) environment).setProperty("processed", "true"); + } + } } 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 new file mode 100644 index 00000000000..2624d646814 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java @@ -0,0 +1,84 @@ +/* + * 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.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.env.ConfigurableEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EnvironmentPostProcessorsFactory}. + * + * @author Phillip Webb + */ +class EnvironmentPostProcessorsFactoryTests { + + private final DeferredLogFactory logFactory = Supplier::get; + + @Test + void fromSpringFactoriesReturnsFactory() { + EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.fromSpringFactories(null); + List processors = factory.getEnvironmentPostProcessors(this.logFactory); + assertThat(processors).hasSizeGreaterThan(1); + } + + @Test + void ofClassesReturnsFactory() { + EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory + .of(TestEnvironmentPostProcessor.class); + List processors = factory.getEnvironmentPostProcessors(this.logFactory); + assertThat(processors).hasSize(1); + assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); + } + + @Test + 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); + assertThat(processors).hasSize(1); + assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); + } + + static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { + + TestEnvironmentPostProcessor(DeferredLogFactory logFactory) { + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + +} 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 new file mode 100644 index 00000000000..481b5ef02ca --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java @@ -0,0 +1,157 @@ +/* + * 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.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.env.ConfigurableEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link ReflectionEnvironmentPostProcessorsFactory}. + * + * @author Phillip Webb + */ +class ReflectionEnvironmentPostProcessorsFactoryTests { + + private final DeferredLogFactory logFactory = Supplier::get; + + @Test + void createWithClassesCreatesFactory() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestEnvironmentPostProcessor.class); + assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); + } + + @Test + void createWithClassNamesArrayCreatesFactory() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestEnvironmentPostProcessor.class.getName()); + assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); + } + + @Test + void createWithClassNamesListCreatesFactory() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + Arrays.asList(TestEnvironmentPostProcessor.class.getName())); + assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); + } + + @Test + void getEnvironmentPostProcessorsWhenHasDefaultConstructorCreatesPostProcessors() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestEnvironmentPostProcessor.class.getName()); + assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); + } + + @Test + void getEnvironmentPostProcessorsWhenHasLogFactoryConstructorCreatesPostProcessors() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestLogFactoryEnvironmentPostProcessor.class.getName()); + assertThatFactory(factory).createsSinglePostProcessor(TestLogFactoryEnvironmentPostProcessor.class); + } + + @Test + void getEnvironmentPostProcessorsWhenHasLogConstructorCreatesPostProcessors() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + TestLogEnvironmentPostProcessor.class.getName()); + assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class); + } + + @Test + void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() { + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + BadEnvironmentPostProcessor.class.getName()); + assertThatIllegalArgumentException().isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory)) + .withMessageContaining("Unable to instantiate"); + } + + private EnvironmentPostProcessorsFactoryAssert assertThatFactory(EnvironmentPostProcessorsFactory factory) { + return new EnvironmentPostProcessorsFactoryAssert(factory); + } + + class EnvironmentPostProcessorsFactoryAssert { + + private EnvironmentPostProcessorsFactory factory; + + EnvironmentPostProcessorsFactoryAssert(EnvironmentPostProcessorsFactory factory) { + this.factory = factory; + } + + void createsSinglePostProcessor(Class expectedType) { + List processors = this.factory + .getEnvironmentPostProcessors(ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory); + assertThat(processors).hasSize(1); + assertThat(processors.get(0)).isInstanceOf(expectedType); + } + + } + + static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + + static class TestLogFactoryEnvironmentPostProcessor implements EnvironmentPostProcessor { + + TestLogFactoryEnvironmentPostProcessor(DeferredLogFactory logFactory) { + assertThat(logFactory).isNotNull(); + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + + static class TestLogEnvironmentPostProcessor implements EnvironmentPostProcessor { + + TestLogEnvironmentPostProcessor(Log log) { + assertThat(log).isNotNull(); + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + + static class BadEnvironmentPostProcessor implements EnvironmentPostProcessor { + + BadEnvironmentPostProcessor(InputStream inputStream) { + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + } + + } + +}