From 6e749ef2767ce488ffdfe0b1a801e75ea86cb243 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 17 Jul 2018 15:43:57 +0200 Subject: [PATCH] Improve NoSuchBeanDefinitionFailureAnalyzer to handle null beans Previously, if a user defines a `@Bean` with a method that returns `null`, injection by type will ignore that definition but the report doesn't mention that candidate. This commit improves the failure analyzer to look for user-defined beans as well, detecting beans that are `null` matching the requested type and including them in the report. Closes gh-13531 --- .../NoSuchBeanDefinitionFailureAnalyzer.java | 69 ++++++++++++++++++- ...uchBeanDefinitionFailureAnalyzerTests.java | 40 ++++++++++- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java index 4bfd577051..2b9237fd8f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java @@ -26,7 +26,10 @@ import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; @@ -78,18 +81,27 @@ class NoSuchBeanDefinitionFailureAnalyzer } List autoConfigurationResults = getAutoConfigurationResults( cause); + List userConfigurationResults = getUserConfigurationResults( + cause); StringBuilder message = new StringBuilder(); message.append(String.format("%s required %s that could not be found.%n", (description != null ? description : "A component"), getBeanDescription(cause))); if (!autoConfigurationResults.isEmpty()) { - for (AutoConfigurationResult provider : autoConfigurationResults) { - message.append(String.format("\t- %s%n", provider)); + for (AutoConfigurationResult result : autoConfigurationResults) { + message.append(String.format("\t- %s%n", result)); + } + } + if (!userConfigurationResults.isEmpty()) { + for (UserConfigurationResult result : userConfigurationResults) { + message.append(String.format("\t- %s%n", result)); } } String action = String.format("Consider %s %s in your configuration.", (!autoConfigurationResults.isEmpty() - ? "revisiting the conditions above or defining" : "defining"), + || !userConfigurationResults.isEmpty() + ? "revisiting the entries above or defining" + : "defining"), getBeanDescription(cause)); return new FailureAnalysis(message.toString(), action, cause); } @@ -122,6 +134,30 @@ class NoSuchBeanDefinitionFailureAnalyzer return results; } + private List getUserConfigurationResults( + NoSuchBeanDefinitionException cause) { + List results = new ArrayList<>(); + ResolvableType type = cause.getResolvableType(); + if (type != null) { + for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, cause.getResolvableType())) { + boolean nullBean = this.beanFactory.getBean(beanName).equals(null); + results.add(new UserConfigurationResult( + getFactoryMethodMetadata(beanName), nullBean)); + } + } + return results; + + } + + private MethodMetadata getFactoryMethodMetadata(String beanName) { + BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(beanName); + if (beanDefinition instanceof AnnotatedBeanDefinition) { + return ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata(); + } + return null; + } + private void collectReportedConditionOutcomes(NoSuchBeanDefinitionException cause, List results) { this.report.getConditionAndOutcomesBySource().forEach( @@ -296,4 +332,31 @@ class NoSuchBeanDefinitionFailureAnalyzer } + private static class UserConfigurationResult { + + private final MethodMetadata methodMetadata; + + private final boolean nullBean; + + UserConfigurationResult(MethodMetadata methodMetadata, boolean nullBean) { + this.methodMetadata = methodMetadata; + this.nullBean = nullBean; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("User-defined bean"); + if (this.methodMetadata != null) { + sb.append(String.format(" method '%s' in '%s'", + this.methodMetadata.getMethodName(), ClassUtils.getShortName( + this.methodMetadata.getDeclaringClassName()))); + } + if (this.nullBean) { + sb.append(" ignored as the bean value is null"); + } + return sb.toString(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzerTests.java index 6ceb8e3e52..98880bed59 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzerTests.java @@ -184,6 +184,17 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { assertActionMissingName(analysis, "test-string"); } + @Test + public void failureAnalysisForNullBeanByType() { + FailureAnalysis analysis = analyzeFailure( + createFailure(StringNullBeanConfiguration.class)); + assertDescriptionConstructorMissingType(analysis, StringHandler.class, 0, + String.class); + assertUserDefinedBean(analysis, "as the bean value is null", + TestNullBeanConfiguration.class, "string"); + assertActionMissingType(analysis, String.class); + } + private void assertDescriptionConstructorMissingType(FailureAnalysis analysis, Class component, int index, Class type) { String expected = String.format( @@ -195,14 +206,14 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { private void assertActionMissingType(FailureAnalysis analysis, Class type) { assertThat(analysis.getAction()).startsWith(String.format( - "Consider revisiting the conditions above or defining a bean of type '%s' " + "Consider revisiting the entries above or defining a bean of type '%s' " + "in your configuration.", type.getName())); } private void assertActionMissingName(FailureAnalysis analysis, String name) { assertThat(analysis.getAction()).startsWith(String.format( - "Consider revisiting the conditions above or defining a bean named '%s' " + "Consider revisiting the entries above or defining a bean named '%s' " + "in your configuration.", name)); } @@ -223,6 +234,14 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { assertThat(analysis.getDescription()).contains(description); } + private void assertUserDefinedBean(FailureAnalysis analysis, String description, + Class target, String methodName) { + String expected = String.format("User-defined bean method '%s' in '%s' ignored", + methodName, ClassUtils.getShortName(target)); + assertThat(analysis.getDescription()).contains(expected); + assertThat(analysis.getDescription()).contains(description); + } + private static void addExclusions(NoSuchBeanDefinitionFailureAnalyzer analyzer, Class... classes) { ConditionEvaluationReport report = (ConditionEvaluationReport) new DirectFieldAccessor( @@ -305,6 +324,13 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { } + @Configuration + @ImportAutoConfiguration(TestNullBeanConfiguration.class) + @Import(StringHandler.class) + protected static class StringNullBeanConfiguration { + + } + @Configuration public static class TestPropertyAutoConfiguration { @@ -344,6 +370,16 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { } + @Configuration + public static class TestNullBeanConfiguration { + + @Bean + public String string() { + return null; + } + + } + protected static class StringHandler { public StringHandler(String foo) {