Commit eb8b77e3 authored by Andy Wilkinson's avatar Andy Wilkinson

Improve analysis of failures due to NoUniqueBeanDefinitionException

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