Improve analysis of failures due to NoUniqueBeanDefinitionException
Closes gh-5683
This commit is contained in:
@@ -139,7 +139,7 @@
|
||||
<snakeyaml.version>1.17</snakeyaml.version>
|
||||
<solr.version>5.5.0</solr.version>
|
||||
<spock.version>1.0-groovy-2.4</spock.version>
|
||||
<spring.version>4.3.0.RC1</spring.version>
|
||||
<spring.version>4.3.0.BUILD-SNAPSHOT</spring.version>
|
||||
<spring-amqp.version>1.6.0.M2</spring-amqp.version>
|
||||
<spring-cloud-connectors.version>1.2.2.RELEASE</spring-cloud-connectors.version>
|
||||
<spring-batch.version>3.0.6.RELEASE</spring-batch.version>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.diagnostics.analyzer;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -36,7 +38,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
class NoUniqueBeanDefinitionFailureAnalyzer
|
||||
extends AbstractFailureAnalyzer<NoUniqueBeanDefinitionException>
|
||||
implements BeanFactoryAware {
|
||||
|
||||
@@ -51,9 +53,8 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
@Override
|
||||
protected FailureAnalysis analyze(Throwable rootFailure,
|
||||
NoUniqueBeanDefinitionException cause) {
|
||||
UnsatisfiedDependencyException unsatisfiedDependency = findUnsatisfiedDependencyException(
|
||||
rootFailure);
|
||||
if (unsatisfiedDependency == null) {
|
||||
String consumerDescription = getConsumerDescription(rootFailure);
|
||||
if (consumerDescription == null) {
|
||||
return null;
|
||||
}
|
||||
String[] beanNames = extractBeanNames(cause);
|
||||
@@ -62,7 +63,7 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
}
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(String.format("%s required a single bean, but %d were found:%n",
|
||||
getConsumerDescription(unsatisfiedDependency), beanNames.length));
|
||||
consumerDescription, beanNames.length));
|
||||
for (String beanName : beanNames) {
|
||||
try {
|
||||
BeanDefinition beanDefinition = this.beanFactory
|
||||
@@ -90,13 +91,37 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
cause);
|
||||
}
|
||||
|
||||
private String getConsumerDescription(Throwable ex) {
|
||||
UnsatisfiedDependencyException unsatisfiedDependency = findUnsatisfiedDependencyException(
|
||||
ex);
|
||||
if (unsatisfiedDependency != null) {
|
||||
return getConsumerDescription(unsatisfiedDependency);
|
||||
}
|
||||
BeanInstantiationException beanInstantiationException = findBeanInstantiationException(
|
||||
ex);
|
||||
if (beanInstantiationException != null) {
|
||||
return getConsumerDescription(beanInstantiationException);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private UnsatisfiedDependencyException findUnsatisfiedDependencyException(
|
||||
Throwable root) {
|
||||
return findMostNestedCause(root, UnsatisfiedDependencyException.class);
|
||||
}
|
||||
|
||||
private BeanInstantiationException findBeanInstantiationException(Throwable root) {
|
||||
return findMostNestedCause(root, BeanInstantiationException.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Exception> T findMostNestedCause(Throwable root,
|
||||
Class<T> causeType) {
|
||||
Throwable candidate = root;
|
||||
UnsatisfiedDependencyException mostNestedMatch = null;
|
||||
T mostNestedMatch = null;
|
||||
while (candidate != null) {
|
||||
if (candidate instanceof UnsatisfiedDependencyException) {
|
||||
mostNestedMatch = (UnsatisfiedDependencyException) candidate;
|
||||
if (causeType.isAssignableFrom(candidate.getClass())) {
|
||||
mostNestedMatch = (T) candidate;
|
||||
}
|
||||
candidate = candidate.getCause();
|
||||
}
|
||||
@@ -107,7 +132,7 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
InjectionPoint injectionPoint = ex.getInjectionPoint();
|
||||
if (injectionPoint != null) {
|
||||
if (injectionPoint.getField() != null) {
|
||||
return String.format("Field '%s' in %s",
|
||||
return String.format("Field %s in %s",
|
||||
injectionPoint.getField().getName(),
|
||||
injectionPoint.getField().getDeclaringClass().getName());
|
||||
}
|
||||
@@ -118,7 +143,7 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
injectionPoint.getMethodParameter().getDeclaringClass()
|
||||
.getName());
|
||||
}
|
||||
return String.format("Parameter %d of method '%s' in %s",
|
||||
return String.format("Parameter %d of method %s in %s",
|
||||
injectionPoint.getMethodParameter().getParameterIndex(),
|
||||
injectionPoint.getMethodParameter().getMethod().getName(),
|
||||
injectionPoint.getMethodParameter().getDeclaringClass()
|
||||
@@ -128,6 +153,18 @@ class NoUniqueBeanDefinitionExceptionFailureAnalyzer
|
||||
return ex.getResourceDescription();
|
||||
}
|
||||
|
||||
private String getConsumerDescription(BeanInstantiationException ex) {
|
||||
if (ex.getConstructingMethod() != null) {
|
||||
return String.format("Method %s in %s", ex.getConstructingMethod().getName(),
|
||||
ex.getConstructingMethod().getDeclaringClass().getName());
|
||||
}
|
||||
if (ex.getConstructor() != null) {
|
||||
return String.format("Constructor in %s", ClassUtils
|
||||
.getUserClass(ex.getConstructor().getDeclaringClass()).getName());
|
||||
}
|
||||
return ex.getBeanClass().getName();
|
||||
}
|
||||
|
||||
private String[] extractBeanNames(NoUniqueBeanDefinitionException cause) {
|
||||
if (cause.getMessage().indexOf("but found") > -1) {
|
||||
return StringUtils.commaDelimitedListToStringArray(cause.getMessage()
|
||||
@@ -33,8 +33,10 @@ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
|
||||
|
||||
# Failure Analyzers
|
||||
org.springframework.boot.diagnostics.FailureAnalyzer=\
|
||||
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer
|
||||
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,
|
||||
|
||||
# FailureAnalysisReporters
|
||||
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
|
||||
|
||||
@@ -18,7 +18,8 @@ package org.springframework.boot.diagnostics.analyzer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.boot.diagnostics.analyzer.nounique.TestBean;
|
||||
@@ -32,21 +33,20 @@ import org.springframework.context.annotation.ImportResource;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link NoUniqueBeanDefinitionExceptionFailureAnalyzer}.
|
||||
* Tests for {@link NoUniqueBeanDefinitionFailureAnalyzer}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
public class NoUniqueBeanDefinitionFailureAnalyzerTests {
|
||||
|
||||
private final NoUniqueBeanDefinitionExceptionFailureAnalyzer analyzer = new NoUniqueBeanDefinitionExceptionFailureAnalyzer();
|
||||
private final NoUniqueBeanDefinitionFailureAnalyzer analyzer = new NoUniqueBeanDefinitionFailureAnalyzer();
|
||||
|
||||
@Test
|
||||
public void failureAnalysisForFieldConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(FieldConsumer.class));
|
||||
System.out.println(failureAnalysis.getDescription());
|
||||
assertThat(failureAnalysis.getDescription())
|
||||
.startsWith("Field 'testBean' in " + FieldConsumer.class.getName()
|
||||
.startsWith("Field testBean in " + FieldConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
@@ -55,9 +55,28 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
public void failureAnalysisForMethodConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(MethodConsumer.class));
|
||||
System.out.println(failureAnalysis.getDescription());
|
||||
assertThat(failureAnalysis.getDescription()).startsWith(
|
||||
"Parameter 0 of method 'consumer' in " + MethodConsumer.class.getName()
|
||||
"Parameter 0 of method consumer in " + MethodConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failureAnalysisForConstructorConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(ConstructorConsumer.class));
|
||||
assertThat(failureAnalysis.getDescription()).startsWith(
|
||||
"Parameter 0 of constructor in " + ConstructorConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failureAnalysisForObjectProviderMethodConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(ObjectProviderMethodConsumer.class));
|
||||
assertThat(failureAnalysis.getDescription()).startsWith(
|
||||
"Method consumer in " + ObjectProviderMethodConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
@@ -66,14 +85,23 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
public void failureAnalysisForXmlConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(XmlConsumer.class));
|
||||
System.out.println(failureAnalysis.getDescription());
|
||||
assertThat(failureAnalysis.getDescription()).startsWith(
|
||||
"Parameter 0 of constructor in " + TestBeanConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
|
||||
private UnsatisfiedDependencyException createFailure(Class<?> consumer) {
|
||||
@Test
|
||||
public void failureAnalysisForObjectProviderConstructorConsumer() {
|
||||
FailureAnalysis failureAnalysis = analyzeFailure(
|
||||
createFailure(ObjectProviderConstructorConsumer.class));
|
||||
assertThat(failureAnalysis.getDescription()).startsWith(
|
||||
"Constructor in " + ObjectProviderConstructorConsumer.class.getName()
|
||||
+ " required a single bean, but 6 were found:");
|
||||
assertFoundBeans(failureAnalysis);
|
||||
}
|
||||
|
||||
private BeanCreationException createFailure(Class<?> consumer) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(DuplicateBeansProducer.class, consumer);
|
||||
context.setParent(new AnnotationConfigApplicationContext(ParentProducer.class));
|
||||
@@ -81,7 +109,7 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
context.refresh();
|
||||
return null;
|
||||
}
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
catch (BeanCreationException ex) {
|
||||
this.analyzer.setBeanFactory(context.getBeanFactory());
|
||||
return ex;
|
||||
}
|
||||
@@ -90,7 +118,7 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
}
|
||||
}
|
||||
|
||||
private FailureAnalysis analyzeFailure(UnsatisfiedDependencyException failure) {
|
||||
private FailureAnalysis analyzeFailure(BeanCreationException failure) {
|
||||
return this.analyzer.analyze(failure);
|
||||
}
|
||||
|
||||
@@ -144,6 +172,24 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ObjectProviderConstructorConsumer {
|
||||
|
||||
ObjectProviderConstructorConsumer(ObjectProvider<TestBean> objectProvider) {
|
||||
objectProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConstructorConsumer {
|
||||
|
||||
ConstructorConsumer(TestBean testBean) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class MethodConsumer {
|
||||
|
||||
@@ -154,6 +200,17 @@ public class NoUniqueBeanDefinitionExceptionFailureAnalyzerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ObjectProviderMethodConsumer {
|
||||
|
||||
@Bean
|
||||
String consumer(ObjectProvider<TestBean> testBeanProvider) {
|
||||
testBeanProvider.getIfAvailable();
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ImportResource("/org/springframework/boot/diagnostics/analyzer/nounique/consumer.xml")
|
||||
static class XmlConsumer {
|
||||
Reference in New Issue
Block a user