From f9b6c1ab4d81eaf0de320162c2ee932061f7bd27 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 10 Oct 2018 11:04:46 +0100 Subject: [PATCH] Include more information in NoSuchBeanDefinition failure analysis Closes gh-13594 --- .../NoSuchBeanDefinitionFailureAnalyzer.java | 55 +++++++++++-------- ...uchBeanDefinitionFailureAnalyzerTests.java | 40 ++++++++++++-- 2 files changed, 67 insertions(+), 28 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 0d08f1ee1a..31bb9f5c28 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 @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.diagnostics.analyzer; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -30,6 +31,7 @@ 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.UnsatisfiedDependencyException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -89,11 +91,23 @@ class NoSuchBeanDefinitionFailureAnalyzer message.append(String.format("%s required %s that could not be found.%n", (description != null) ? description : "A component", getBeanDescription(cause))); - for (AutoConfigurationResult result : autoConfigurationResults) { - message.append(String.format("\t- %s%n", result)); + List injectionAnnotations = findInjectionAnnotations(rootFailure); + if (!injectionAnnotations.isEmpty()) { + message.append(String + .format("%nThe injection point has the following annotations:%n")); + for (Annotation injectionAnnotation : injectionAnnotations) { + message.append(String.format("\t- %s%n", injectionAnnotation)); + } } - for (UserConfigurationResult result : userConfigurationResults) { - message.append(String.format("\t- %s%n", result)); + if (!autoConfigurationResults.isEmpty() || !userConfigurationResults.isEmpty()) { + message.append(String.format( + "%nThe following candidates were found but could not be injected:%n")); + for (AutoConfigurationResult result : autoConfigurationResults) { + message.append(String.format("\t- %s%n", result)); + } + for (UserConfigurationResult result : userConfigurationResults) { + message.append(String.format("\t- %s%n", result)); + } } String action = String.format("Consider %s %s in your configuration.", (!autoConfigurationResults.isEmpty() @@ -172,7 +186,7 @@ class NoSuchBeanDefinitionFailureAnalyzer if (!conditionAndOutcome.getOutcome().isMatch()) { for (MethodMetadata method : methods) { results.add(new AutoConfigurationResult(method, - conditionAndOutcome.getOutcome(), source.isMethod())); + conditionAndOutcome.getOutcome())); } } } @@ -187,11 +201,21 @@ class NoSuchBeanDefinitionFailureAnalyzer String message = String.format("auto-configuration '%s' was excluded", ClassUtils.getShortName(excludedClass)); results.add(new AutoConfigurationResult(method, - new ConditionOutcome(false, message), false)); + new ConditionOutcome(false, message))); } } } + private List findInjectionAnnotations(Throwable failure) { + UnsatisfiedDependencyException unsatisfiedDependencyException = findCause(failure, + UnsatisfiedDependencyException.class); + if (unsatisfiedDependencyException == null) { + return Collections.emptyList(); + } + return Arrays.asList( + unsatisfiedDependencyException.getInjectionPoint().getAnnotations()); + } + private class Source { private final String className; @@ -212,10 +236,6 @@ class NoSuchBeanDefinitionFailureAnalyzer return this.methodName; } - public boolean isMethod() { - return this.methodName != null; - } - } private class BeanMethods implements Iterable { @@ -303,26 +323,17 @@ class NoSuchBeanDefinitionFailureAnalyzer private final ConditionOutcome conditionOutcome; - private final boolean methodEvaluated; - AutoConfigurationResult(MethodMetadata methodMetadata, - ConditionOutcome conditionOutcome, boolean methodEvaluated) { + ConditionOutcome conditionOutcome) { this.methodMetadata = methodMetadata; this.conditionOutcome = conditionOutcome; - this.methodEvaluated = methodEvaluated; } @Override public String toString() { - if (this.methodEvaluated) { - return String.format("Bean method '%s' in '%s' not loaded because %s", - this.methodMetadata.getMethodName(), - ClassUtils.getShortName( - this.methodMetadata.getDeclaringClassName()), - this.conditionOutcome.getMessage()); - } - return String.format("Bean method '%s' not loaded because %s", + return String.format("Bean method '%s' in '%s' not loaded because %s", this.methodMetadata.getMethodName(), + ClassUtils.getShortName(this.methodMetadata.getDeclaringClassName()), this.conditionOutcome.getMessage()); } 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 98880bed59..b075be3f13 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 @@ -27,6 +27,7 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -127,7 +128,7 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { assertDescriptionConstructorMissingType(analysis, StringHandler.class, 0, String.class); assertClassDisabled(analysis, "did not find required class 'com.example.FooBar'", - "string"); + "string", ClassUtils.getShortName(TestTypeClassAutoConfiguration.class)); assertActionMissingType(analysis, String.class); } @@ -142,7 +143,7 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { .getShortName(TestPropertyAutoConfiguration.class.getName()); assertClassDisabled(analysis, String.format("auto-configuration '%s' was excluded", configClass), - "string"); + "string", ClassUtils.getShortName(TestPropertyAutoConfiguration.class)); assertActionMissingType(analysis, String.class); } @@ -156,7 +157,7 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { "did not find property 'spring.string.enabled'", TestPropertyAutoConfiguration.class, "string"); assertClassDisabled(analysis, "did not find required class 'com.example.FooBar'", - "string"); + "string", ClassUtils.getShortName(TestPropertyAutoConfiguration.class)); assertActionMissingType(analysis, String.class); } @@ -195,6 +196,14 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { assertActionMissingType(analysis, String.class); } + @Test + public void failureAnalysisForUnmatchedQualfier() { + FailureAnalysis analysis = analyzeFailure( + createFailure(QualifiedBeanConfiguration.class)); + assertThat(analysis.getDescription()).contains( + "@org.springframework.beans.factory.annotation.Qualifier(value=alpha)"); + } + private void assertDescriptionConstructorMissingType(FailureAnalysis analysis, Class component, int index, Class type) { String expected = String.format( @@ -227,9 +236,9 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { } private void assertClassDisabled(FailureAnalysis analysis, String description, - String methodName) { - String expected = String.format("Bean method '%s' not loaded because", - methodName); + String methodName, String className) { + String expected = String.format("Bean method '%s' in '%s' not loaded because", + methodName, className); assertThat(analysis.getDescription()).contains(expected); assertThat(analysis.getDescription()).contains(description); } @@ -380,6 +389,25 @@ public class NoSuchBeanDefinitionFailureAnalyzerTests { } + @Configuration + public static class QualifiedBeanConfiguration { + + @Bean + public String consumer(@Qualifier("alpha") Thing thing) { + return "consumer"; + } + + @Bean + public Thing producer() { + return new Thing(); + } + + class Thing { + + } + + } + protected static class StringHandler { public StringHandler(String foo) {