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 @@
<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 {
......
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