Improve diagnostics when a Bean Override cannot be selected by type

See gh-34004
Closes gh-34006

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
Yanming Zhou
2024-11-26 15:04:37 +08:00
committed by Sam Brannen
parent 2b4c7d09b0
commit cf46f391d7
5 changed files with 21 additions and 13 deletions

View File

@@ -59,6 +59,7 @@ import org.springframework.util.Assert;
* @author Simon Baslé * @author Simon Baslé
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen * @author Sam Brannen
* @author Yanming Zhou
* @since 6.2 * @since 6.2
*/ */
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@@ -67,6 +68,9 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE; private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
private static final String unableToOverrideByTypeDiagnosticsMessage = " If the bean is defined from a @Bean method,"
+ " please make sure the return type is the most specific type (recommended) or type can be assigned to %s";
private final Set<BeanOverrideHandler> beanOverrideHandlers; private final Set<BeanOverrideHandler> beanOverrideHandlers;
private final BeanOverrideRegistry beanOverrideRegistry; private final BeanOverrideRegistry beanOverrideRegistry;
@@ -170,7 +174,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
Field field = handler.getField(); Field field = handler.getField();
throw new IllegalStateException( throw new IllegalStateException(
"Unable to replace bean: there is no bean with name '%s' and type %s%s." "Unable to replace bean: there is no bean with name '%s' and type %s%s."
.formatted(beanName, handler.getBeanType(), requiredByField(field))); .formatted(beanName, handler.getBeanType(), requiredByField(field, handler.getBeanType())));
} }
// 4) We are creating a bean by-name with the provided beanName. // 4) We are creating a bean by-name with the provided beanName.
} }
@@ -257,7 +261,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
String message = "Unable to select a bean to wrap: "; String message = "Unable to select a bean to wrap: ";
int candidateCount = candidateNames.size(); int candidateCount = candidateNames.size();
if (candidateCount == 0) { if (candidateCount == 0) {
message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field)); message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field, beanType));
} }
else { else {
message += "found %d beans of type %s%s: %s" message += "found %d beans of type %s%s: %s"
@@ -299,7 +303,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
if (requireExistingBean) { if (requireExistingBean) {
throw new IllegalStateException( throw new IllegalStateException(
"Unable to override bean: there are no beans of type %s%s." "Unable to override bean: there are no beans of type %s%s."
.formatted(beanType, requiredByField(field))); .formatted(beanType, requiredByField(field, beanType)));
} }
return null; return null;
} }
@@ -483,4 +487,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
field.getDeclaringClass().getSimpleName(), field.getName()); field.getDeclaringClass().getSimpleName(), field.getName());
} }
private static String requiredByField(@Nullable Field field, ResolvableType requiredBeanType) {
return requiredByField(field) + '.' + unableToOverrideByTypeDiagnosticsMessage.formatted(requiredBeanType);
}
} }

View File

@@ -85,7 +85,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'descriptionBean' \ Unable to replace bean: there is no bean with name 'descriptionBean' \
and type java.lang.String (as required by field 'ByNameTestCase.description')."""); and type java.lang.String (as required by field 'ByNameTestCase.description').""");
} }
@@ -97,7 +97,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'descriptionBean' \ Unable to replace bean: there is no bean with name 'descriptionBean' \
and type java.lang.String (as required by field 'ByNameTestCase.description')."""); and type java.lang.String (as required by field 'ByNameTestCase.description').""");
} }
@@ -144,7 +144,7 @@ class BeanOverrideBeanFactoryPostProcessorTests {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to override bean: there are no beans of type java.lang.Integer \ Unable to override bean: there are no beans of type java.lang.Integer \
(as required by field 'ByTypeTestCase.counter')."""); (as required by field 'ByTypeTestCase.counter').""");
} }

View File

@@ -40,7 +40,7 @@ public class TestBeanTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'beanToOverride' \ Unable to replace bean: there is no bean with name 'beanToOverride' \
and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); and type java.lang.String (as required by field 'FailureByNameLookup.example').""");
} }
@@ -52,7 +52,7 @@ public class TestBeanTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'beanToOverride' \ Unable to replace bean: there is no bean with name 'beanToOverride' \
and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); and type java.lang.String (as required by field 'FailureByNameLookup.example').""");
} }
@@ -63,7 +63,7 @@ public class TestBeanTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to override bean: there are no beans of \ Unable to override bean: there are no beans of \
type %s (as required by field '%s.example').""", type %s (as required by field '%s.example').""",
String.class.getName(), FailureByTypeLookup.class.getSimpleName()); String.class.getName(), FailureByTypeLookup.class.getSimpleName());

View File

@@ -40,7 +40,7 @@ class MockitoBeanConfigurationErrorTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'beanToOverride' \ Unable to replace bean: there is no bean with name 'beanToOverride' \
and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); and type java.lang.String (as required by field 'FailureByNameLookup.example').""");
} }
@@ -52,7 +52,7 @@ class MockitoBeanConfigurationErrorTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to replace bean: there is no bean with name 'beanToOverride' \ Unable to replace bean: there is no bean with name 'beanToOverride' \
and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); and type java.lang.String (as required by field 'FailureByNameLookup.example').""");
} }
@@ -63,7 +63,7 @@ class MockitoBeanConfigurationErrorTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to override bean: there are no beans of \ Unable to override bean: there are no beans of \
type java.lang.String (as required by field 'FailureByTypeLookup.example')."""); type java.lang.String (as required by field 'FailureByTypeLookup.example').""");
} }

View File

@@ -50,7 +50,7 @@ class MockitoSpyBeanConfigurationErrorTests {
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookup.class, context); BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookup.class, context);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(context::refresh) .isThrownBy(context::refresh)
.withMessage(""" .withMessageStartingWith("""
Unable to select a bean to wrap: there are no beans of type java.lang.String \ Unable to select a bean to wrap: there are no beans of type java.lang.String \
(as required by field 'ByTypeSingleLookup.example')."""); (as required by field 'ByTypeSingleLookup.example').""");
} }