From c1fec6967e388a2871a469cc98436e0d8e523697 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 22 Feb 2022 14:18:06 -0600 Subject: [PATCH] Support constructor injection for FailureAnalyzers This commit adds support for instantiating FailureAnalyzer implementations with BeanFactory and/or an Environment constructor arguments and deprecates support for setter injection of these values using BeanFactoryAware and EnvironmentAware. Closes gh-29811 --- ...DataSourceBeanCreationFailureAnalyzer.java | 11 +- .../jooq/NoDslContextBeanFailureAnalyzer.java | 18 ++- ...ionFactoryBeanCreationFailureAnalyzer.java | 10 +- ...ourceBeanCreationFailureAnalyzerTests.java | 6 +- .../NoDslContextBeanFailureAnalyzerTests.java | 14 +-- ...ctoryBeanCreationFailureAnalyzerTests.java | 6 +- .../boot/diagnostics/FailureAnalyzers.java | 107 ++++++++++-------- .../boot/util/Instantiator.java | 48 +++++++- .../diagnostics/FailureAnalyzersTests.java | 101 ++++++++++------- .../boot/util/InstantiatorTests.java | 27 ++++- .../failure-analyzers-tests/basic.factories | 4 - .../broken-analysis.factories | 4 - .../broken-initialization.factories | 4 - 13 files changed, 220 insertions(+), 140 deletions(-) delete mode 100644 spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/basic.factories delete mode 100644 spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-analysis.factories delete mode 100644 spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-initialization.factories diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java index 8ce606a296..19e6449bf2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -20,7 +20,6 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.DataSour import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -33,13 +32,11 @@ import org.springframework.util.StringUtils; * @author Patryk Kostrzewa * @author Stephane Nicoll */ -class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer - implements EnvironmentAware { +class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer { - private Environment environment; + private final Environment environment; - @Override - public void setEnvironment(Environment environment) { + DataSourceBeanCreationFailureAnalyzer(Environment environment) { this.environment = environment; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzer.java index 480d1ba5d9..4cd86b7ac2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,7 @@ package org.springframework.boot.autoconfigure.jooq; import org.jooq.DSLContext; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; @@ -28,9 +26,13 @@ import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.core.Ordered; class NoDslContextBeanFailureAnalyzer extends AbstractFailureAnalyzer - implements Ordered, BeanFactoryAware { + implements Ordered { - private BeanFactory beanFactory; + private final BeanFactory beanFactory; + + NoDslContextBeanFailureAnalyzer(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } @Override protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) { @@ -60,10 +62,4 @@ class NoDslContextBeanFailureAnalyzer extends AbstractFailureAnalyzer implements EnvironmentAware { + extends AbstractFailureAnalyzer { - private Environment environment; + private final Environment environment; - @Override - public void setEnvironment(Environment environment) { + ConnectionFactoryBeanCreationFailureAnalyzer(Environment environment) { this.environment = environment; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java index 47d79f6075..4395457862 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -60,8 +60,8 @@ class DataSourceBeanCreationFailureAnalyzerTests { private FailureAnalysis performAnalysis(Class configuration) { BeanCreationException failure = createFailure(configuration); assertThat(failure).isNotNull(); - DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer(); - failureAnalyzer.setEnvironment(this.environment); + DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer( + this.environment); return failureAnalyzer.analyze(failure); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzerTests.java index d70078b777..a546717b1d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -33,13 +33,12 @@ import static org.assertj.core.api.Assertions.assertThat; */ class NoDslContextBeanFailureAnalyzerTests { - private final NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer(); - @Test void noAnalysisWithoutR2dbcAutoConfiguration() { new ApplicationContextRunner().run((context) -> { - this.failureAnalyzer.setBeanFactory(context.getBeanFactory()); - assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))).isNull(); + NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer( + context.getBeanFactory()); + assertThat(failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))).isNull(); }); } @@ -47,8 +46,9 @@ class NoDslContextBeanFailureAnalyzerTests { void analysisWithR2dbcAutoConfiguration() { new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) .run((context) -> { - this.failureAnalyzer.setBeanFactory(context.getBeanFactory()); - assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))) + NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer( + context.getBeanFactory()); + assertThat(failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))) .isNotNull(); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzerTests.java index 2faacabe37..4148e43bf1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -58,8 +58,8 @@ class ConnectionFactoryBeanCreationFailureAnalyzerTests { private FailureAnalysis performAnalysis(Class configuration) { BeanCreationException failure = createFailure(configuration); assertThat(failure).isNotNull(); - ConnectionFactoryBeanCreationFailureAnalyzer failureAnalyzer = new ConnectionFactoryBeanCreationFailureAnalyzer(); - failureAnalyzer.setEnvironment(this.environment); + ConnectionFactoryBeanCreationFailureAnalyzer failureAnalyzer = new ConnectionFactoryBeanCreationFailureAnalyzer( + this.environment); return failureAnalyzer.analyze(failure); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java index 88403e06ca..df040bfb86 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ package org.springframework.boot.diagnostics; -import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -26,26 +25,27 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.boot.SpringBootExceptionReporter; +import org.springframework.boot.util.Instantiator; +import org.springframework.boot.util.Instantiator.FailureHandler; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.EnvironmentAware; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.Environment; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.log.LogMessage; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** * Utility to trigger {@link FailureAnalyzer} and {@link FailureAnalysisReporter} * instances loaded from {@code spring.factories}. *

- * A {@code FailureAnalyzer} that requires access to the {@link BeanFactory} in order to - * perform its analysis can implement {@code BeanFactoryAware} to have the - * {@code BeanFactory} injected prior to {@link FailureAnalyzer#analyze(Throwable)} being - * called. + * A {@code FailureAnalyzer} that requires access to the {@link BeanFactory} or + * {@link Environment} in order to perform its analysis can implement a constructor that + * accepts arguments of one or both of these types. * * @author Andy Wilkinson * @author Phillip Webb * @author Stephane Nicoll + * @author Scott Frederick */ final class FailureAnalyzers implements SpringBootExceptionReporter { @@ -56,54 +56,60 @@ final class FailureAnalyzers implements SpringBootExceptionReporter { private final List analyzers; FailureAnalyzers(ConfigurableApplicationContext context) { - this(context, null); + this(context, SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, getClassLoader(context))); } - FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) { - this.classLoader = (classLoader != null) ? classLoader : getClassLoader(context); - this.analyzers = loadFailureAnalyzers(context, this.classLoader); + FailureAnalyzers(ConfigurableApplicationContext context, List classNames) { + this.classLoader = getClassLoader(context); + this.analyzers = loadFailureAnalyzers(classNames, context); } - private ClassLoader getClassLoader(ConfigurableApplicationContext context) { + private static ClassLoader getClassLoader(ConfigurableApplicationContext context) { return (context != null) ? context.getClassLoader() : null; } - private List loadFailureAnalyzers(ConfigurableApplicationContext context, - ClassLoader classLoader) { - List classNames = SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, classLoader); - List analyzers = new ArrayList<>(); - for (String className : classNames) { - try { - FailureAnalyzer analyzer = createAnalyzer(context, className); - if (analyzer != null) { - analyzers.add(analyzer); - } - } - catch (Throwable ex) { - logger.trace(LogMessage.format("Failed to load %s", className), ex); - } - } - AnnotationAwareOrderComparator.sort(analyzers); - return analyzers; + private List loadFailureAnalyzers(List classNames, + ConfigurableApplicationContext context) { + Instantiator instantiator = new Instantiator<>(FailureAnalyzer.class, + (availableParameters) -> { + if (context != null) { + availableParameters.add(BeanFactory.class, context.getBeanFactory()); + availableParameters.add(Environment.class, context.getEnvironment()); + } + }, new LoggingInstantiationFailureHandler()); + List analyzers = instantiator.instantiate(this.classLoader, classNames); + return handleAwareAnalyzers(analyzers, context); } - private FailureAnalyzer createAnalyzer(ConfigurableApplicationContext context, String className) throws Exception { - Constructor constructor = ClassUtils.forName(className, this.classLoader).getDeclaredConstructor(); - ReflectionUtils.makeAccessible(constructor); - FailureAnalyzer analyzer = (FailureAnalyzer) constructor.newInstance(); - if (analyzer instanceof BeanFactoryAware || analyzer instanceof EnvironmentAware) { + private List handleAwareAnalyzers(List analyzers, + ConfigurableApplicationContext context) { + List awareAnalyzers = analyzers.stream() + .filter((analyzer) -> analyzer instanceof BeanFactoryAware || analyzer instanceof EnvironmentAware) + .collect(Collectors.toList()); + if (!awareAnalyzers.isEmpty()) { + String awareAnalyzerNames = StringUtils.collectionToCommaDelimitedString(awareAnalyzers.stream() + .map((analyzer) -> analyzer.getClass().getName()).collect(Collectors.toList())); + logger.warn(LogMessage.format( + "FailureAnalyzers [%s] implement BeanFactoryAware or EnvironmentAware." + + "Support for these interfaces on FailureAnalyzers is deprecated, " + + "and will be removed in a future release." + + "Instead provide a constructor that accepts BeanFactory or Environment parameters.", + awareAnalyzerNames)); if (context == null) { - logger.trace(LogMessage.format("Skipping %s due to missing context", className)); - return null; - } - if (analyzer instanceof BeanFactoryAware) { - ((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory()); - } - if (analyzer instanceof EnvironmentAware) { - ((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment()); + logger.trace(LogMessage.format("Skipping [%s] due to missing context", awareAnalyzerNames)); + return analyzers.stream().filter((analyzer) -> !awareAnalyzers.contains(analyzer)) + .collect(Collectors.toList()); } + awareAnalyzers.forEach((analyzer) -> { + if (analyzer instanceof BeanFactoryAware) { + ((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory()); + } + if (analyzer instanceof EnvironmentAware) { + ((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment()); + } + }); } - return analyzer; + return analyzers; } @Override @@ -139,4 +145,13 @@ final class FailureAnalyzers implements SpringBootExceptionReporter { return true; } + static class LoggingInstantiationFailureHandler implements FailureHandler { + + @Override + public void handleFailure(Class type, String implementationName, Throwable failure) { + logger.trace(LogMessage.format("Skipping %s: %s", implementationName, failure.getMessage())); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java index aaca052007..88d15ef60f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -40,6 +40,7 @@ import org.springframework.util.ReflectionUtils; * * @param the type to instantiate * @author Phillip Webb + * @author Scott Frederick * @since 2.4.0 */ public class Instantiator { @@ -47,18 +48,39 @@ public class Instantiator { private static final Comparator> CONSTRUCTOR_COMPARATOR = Comparator .>comparingInt(Constructor::getParameterCount).reversed(); + private static final FailureHandler throwingFailureHandler = (type, implementationName, failure) -> { + throw new IllegalArgumentException("Unable to instantiate " + implementationName + " [" + type.getName() + "]", + failure); + }; + private final Class type; private final Map, Function, Object>> availableParameters; + private final FailureHandler failureHandler; + /** * Create a new {@link Instantiator} instance for the given type. * @param type the type to instantiate * @param availableParameters consumer used to register available parameters */ public Instantiator(Class type, Consumer availableParameters) { + this(type, availableParameters, throwingFailureHandler); + } + + /** + * Create a new {@link Instantiator} instance for the given type. + * @param type the type to instantiate + * @param availableParameters consumer used to register available parameters + * @param failureHandler a {@link FailureHandler} that will be called in case of + * failure when instantiating objects + * @since 2.7.0 + */ + public Instantiator(Class type, Consumer availableParameters, + FailureHandler failureHandler) { this.type = type; this.availableParameters = getAvailableParameters(availableParameters); + this.failureHandler = failureHandler; } private Map, Function, Object>> getAvailableParameters( @@ -127,8 +149,8 @@ public class Instantiator { return instantiate(type); } catch (Throwable ex) { - throw new IllegalArgumentException( - "Unable to instantiate " + this.type.getName() + " [" + typeSupplier.getName() + "]", ex); + this.failureHandler.handleFailure(this.type, typeSupplier.getName(), ex); + return null; } } @@ -143,7 +165,7 @@ public class Instantiator { return (T) constructor.newInstance(args); } } - throw new IllegalAccessException("Unable to find suitable constructor"); + throw new IllegalAccessException("Class [" + type.getName() + "] has no suitable constructor"); } private Object[] getArgs(Class[] parameterTypes) { @@ -231,4 +253,22 @@ public class Instantiator { } + /** + * Strategy for handling a failure that occurs when instantiating a type. + * + * @since 2.7.0 + */ + public interface FailureHandler { + + /** + * Handle the {@code failure} that occurred when instantiating the {@code type} + * that was expected to be of the given {@code typeSupplier}. + * @param type the type + * @param implementationName the name of the implementation type + * @param failure the failure that occurred + */ + void handleFailure(Class type, String implementationName, Throwable failure); + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/FailureAnalyzersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/FailureAnalyzersTests.java index 8fae3980fd..f45931ccdd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/FailureAnalyzersTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/FailureAnalyzersTests.java @@ -16,20 +16,22 @@ package org.springframework.boot.diagnostics; -import java.io.IOException; -import java.net.URL; -import java.util.Enumeration; +import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.boot.testsupport.system.CapturedOutput; +import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.env.Environment; -import static org.mockito.ArgumentMatchers.any; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -39,11 +41,15 @@ import static org.mockito.Mockito.times; * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Scott Frederick */ +@ExtendWith(OutputCaptureExtension.class) class FailureAnalyzersTests { private static AwareFailureAnalyzer failureAnalyzer; + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + @BeforeEach void configureMock() { failureAnalyzer = mock(AwareFailureAnalyzer.class); @@ -52,53 +58,75 @@ class FailureAnalyzersTests { @Test void analyzersAreLoadedAndCalled() { RuntimeException failure = new RuntimeException(); - analyzeAndReport("basic.factories", failure); + analyzeAndReport(failure, BasicFailureAnalyzer.class.getName(), BasicFailureAnalyzer.class.getName()); + then(failureAnalyzer).should(times(2)).analyze(failure); + } + + @Test + void analyzerIsConstructedWithBeanFactory(CapturedOutput output) { + RuntimeException failure = new RuntimeException(); + analyzeAndReport(failure, BasicFailureAnalyzer.class.getName(), + BeanFactoryConstructorFailureAnalyzer.class.getName()); then(failureAnalyzer).should(times(2)).analyze(failure); + assertThat(output).doesNotContain("implement BeanFactoryAware or EnvironmentAware"); } @Test - void beanFactoryIsInjectedIntoBeanFactoryAwareFailureAnalyzers() { + void analyzerIsConstructedWithEnvironment(CapturedOutput output) { RuntimeException failure = new RuntimeException(); - analyzeAndReport("basic.factories", failure); - then(failureAnalyzer).should().setBeanFactory(any(BeanFactory.class)); + analyzeAndReport(failure, BasicFailureAnalyzer.class.getName(), + EnvironmentConstructorFailureAnalyzer.class.getName()); + then(failureAnalyzer).should(times(2)).analyze(failure); + assertThat(output).doesNotContain("implement BeanFactoryAware or EnvironmentAware"); + } + + @Test + void beanFactoryIsInjectedIntoBeanFactoryAwareFailureAnalyzers(CapturedOutput output) { + RuntimeException failure = new RuntimeException(); + analyzeAndReport(failure, BasicFailureAnalyzer.class.getName(), StandardAwareFailureAnalyzer.class.getName()); + then(failureAnalyzer).should().setBeanFactory(same(this.context.getBeanFactory())); + assertThat(output).contains("FailureAnalyzers [" + StandardAwareFailureAnalyzer.class.getName() + + "] implement BeanFactoryAware or EnvironmentAware."); } @Test void environmentIsInjectedIntoEnvironmentAwareFailureAnalyzers() { RuntimeException failure = new RuntimeException(); - analyzeAndReport("basic.factories", failure); - then(failureAnalyzer).should().setEnvironment(any(Environment.class)); + analyzeAndReport(failure, BasicFailureAnalyzer.class.getName(), StandardAwareFailureAnalyzer.class.getName()); + then(failureAnalyzer).should().setEnvironment(same(this.context.getEnvironment())); } @Test void analyzerThatFailsDuringInitializationDoesNotPreventOtherAnalyzersFromBeingCalled() { RuntimeException failure = new RuntimeException(); - analyzeAndReport("broken-initialization.factories", failure); + analyzeAndReport(failure, BrokenInitializationFailureAnalyzer.class.getName(), + BasicFailureAnalyzer.class.getName()); then(failureAnalyzer).should().analyze(failure); } @Test void analyzerThatFailsDuringAnalysisDoesNotPreventOtherAnalyzersFromBeingCalled() { RuntimeException failure = new RuntimeException(); - analyzeAndReport("broken-analysis.factories", failure); + analyzeAndReport(failure, BrokenAnalysisFailureAnalyzer.class.getName(), BasicFailureAnalyzer.class.getName()); then(failureAnalyzer).should().analyze(failure); } @Test void createWithNullContextSkipsAwareAnalyzers() { RuntimeException failure = new RuntimeException(); - analyzeAndReport("basic.factories", failure, null); + analyzeAndReport(failure, (AnnotationConfigApplicationContext) null, BasicFailureAnalyzer.class.getName(), + BeanFactoryConstructorFailureAnalyzer.class.getName(), + EnvironmentConstructorFailureAnalyzer.class.getName(), StandardAwareFailureAnalyzer.class.getName()); then(failureAnalyzer).should().analyze(failure); } - private void analyzeAndReport(String factoriesName, Throwable failure) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - analyzeAndReport(factoriesName, failure, context); + private void analyzeAndReport(Throwable failure, String... factoryNames) { + analyzeAndReport(failure, this.context, factoryNames); } - private void analyzeAndReport(String factoriesName, Throwable failure, AnnotationConfigApplicationContext context) { - ClassLoader classLoader = new CustomSpringFactoriesClassLoader(factoriesName); - new FailureAnalyzers(context, classLoader).reportException(failure); + private void analyzeAndReport(Throwable failure, AnnotationConfigApplicationContext context, + String... factoryNames) { + new FailureAnalyzers(context, Arrays.asList(factoryNames)).reportException(failure); } static class BasicFailureAnalyzer implements FailureAnalyzer { @@ -133,6 +161,22 @@ class FailureAnalyzersTests { } + static class BeanFactoryConstructorFailureAnalyzer extends BasicFailureAnalyzer { + + BeanFactoryConstructorFailureAnalyzer(BeanFactory beanFactory) { + assertThat(beanFactory).isNotNull(); + } + + } + + static class EnvironmentConstructorFailureAnalyzer extends BasicFailureAnalyzer { + + EnvironmentConstructorFailureAnalyzer(Environment environment) { + assertThat(environment).isNotNull(); + } + + } + interface AwareFailureAnalyzer extends BeanFactoryAware, EnvironmentAware, FailureAnalyzer { } @@ -151,23 +195,4 @@ class FailureAnalyzersTests { } - static class CustomSpringFactoriesClassLoader extends ClassLoader { - - private final String factoriesName; - - CustomSpringFactoriesClassLoader(String factoriesName) { - super(CustomSpringFactoriesClassLoader.class.getClassLoader()); - this.factoriesName = factoriesName; - } - - @Override - public Enumeration getResources(String name) throws IOException { - if ("META-INF/spring.factories".equals(name)) { - return super.getResources("failure-analyzers-tests/" + this.factoriesName); - } - return super.getResources(name); - } - - } - } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java index a622f6de82..25c6f944b7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,23 +22,26 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.boot.util.Instantiator.FailureHandler; import org.springframework.core.Ordered; import org.springframework.core.OverridingClassLoader; import org.springframework.core.annotation.Order; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link Instantiator}. * * @author Phillip Webb + * @author Scott Frederick */ class InstantiatorTests { - private ParamA paramA = new ParamA(); + private final ParamA paramA = new ParamA(); - private ParamB paramB = new ParamB(); + private final ParamB paramB = new ParamB(); private ParamC paramC; @@ -108,6 +111,15 @@ class InstantiatorTests { .withMessageContaining("Unable to instantiate"); } + @Test + void createWithFailureHandlerInvokesFailureHandler() { + assertThatIllegalStateException() + .isThrownBy(() -> new Instantiator<>(WithDefaultConstructor.class, (availableParameters) -> { + }, new CustomFailureHandler()) + .instantiate(Collections.singleton(WithAdditionalConstructor.class.getName()))) + .withMessageContaining("custom failure handler message"); + } + private T createInstance(Class type) { return createInstantiator(type).instantiate(Collections.singleton(type.getName())).get(0); } @@ -182,4 +194,13 @@ class InstantiatorTests { } + class CustomFailureHandler implements FailureHandler { + + @Override + public void handleFailure(Class type, String implementationName, Throwable failure) { + throw new IllegalStateException("custom failure handler message"); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/basic.factories b/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/basic.factories deleted file mode 100644 index 30ca0a3a54..0000000000 --- a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/basic.factories +++ /dev/null @@ -1,4 +0,0 @@ -# Failure Analyzers -org.springframework.boot.diagnostics.FailureAnalyzer=\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$BasicFailureAnalyzer,\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$StandardAwareFailureAnalyzer diff --git a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-analysis.factories b/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-analysis.factories deleted file mode 100644 index b1dce49846..0000000000 --- a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-analysis.factories +++ /dev/null @@ -1,4 +0,0 @@ -# Failure Analyzers -org.springframework.boot.diagnostics.FailureAnalyzer=\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$BrokenAnalysisFailureAnalyzer,\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$BasicFailureAnalyzer diff --git a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-initialization.factories b/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-initialization.factories deleted file mode 100644 index c8b15410bc..0000000000 --- a/spring-boot-project/spring-boot/src/test/resources/failure-analyzers-tests/broken-initialization.factories +++ /dev/null @@ -1,4 +0,0 @@ -# Failure Analyzers -org.springframework.boot.diagnostics.FailureAnalyzer=\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$BrokenInitializationFailureAnalyzer,\ -org.springframework.boot.diagnostics.FailureAnalyzersTests$BasicFailureAnalyzer