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
This commit is contained in:
@@ -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<AutoConfigurationResult> autoConfigurationResults = getAutoConfigurationResults(
|
||||
cause);
|
||||
List<UserConfigurationResult> 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<UserConfigurationResult> getUserConfigurationResults(
|
||||
NoSuchBeanDefinitionException cause) {
|
||||
List<UserConfigurationResult> 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<AutoConfigurationResult> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user