Skip to content

Commit 903078a

Browse files
committed
Do not silently return null when no constructor candidate is found
This commit updates ConstructorOrFactoryMethodResolver to throw an exception if no constructor or factory method can be found for a given bean definition. This prevents code generation to happen on an incomplete view of the bean to instantiate. Closes gh-29052
1 parent 8fbd214 commit 903078a

File tree

2 files changed

+15
-16
lines changed

2 files changed

+15
-16
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/ConstructorOrFactoryMethodResolver.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.lang.reflect.Constructor;
2020
import java.lang.reflect.Executable;
21-
import java.lang.reflect.Field;
2221
import java.lang.reflect.Method;
2322
import java.lang.reflect.Modifier;
2423
import java.util.ArrayList;
@@ -74,7 +73,6 @@ class ConstructorOrFactoryMethodResolver {
7473
}
7574

7675

77-
@Nullable
7876
Executable resolve(BeanDefinition beanDefinition) {
7977
Supplier<ResolvableType> beanType = () -> getBeanType(beanDefinition);
8078
List<ResolvableType> valueTypes = (beanDefinition.hasConstructorArgumentValues() ?
@@ -93,21 +91,22 @@ Executable resolve(BeanDefinition beanDefinition) {
9391
Assert.state(isCompatible, () -> String.format(
9492
"Incompatible target type '%s' for factory bean '%s'",
9593
resolvableType.toClass().getName(), factoryBeanClass.getName()));
96-
return resolveConstructor(() -> ResolvableType.forClass(factoryBeanClass), valueTypes);
94+
Executable executable = resolveConstructor(() -> ResolvableType.forClass(factoryBeanClass), valueTypes);
95+
if (executable != null) {
96+
return executable;
97+
}
98+
throw new IllegalStateException("No suitable FactoryBean constructor found for "
99+
+ beanDefinition + " and argument types " + valueTypes);
100+
97101
}
98102

99103
Executable resolvedConstructor = resolveConstructor(beanType, valueTypes);
100104
if (resolvedConstructor != null) {
101105
return resolvedConstructor;
102106
}
103107

104-
Field field = ReflectionUtils.findField(RootBeanDefinition.class, "resolvedConstructorOrFactoryMethod");
105-
if (field != null) {
106-
ReflectionUtils.makeAccessible(field);
107-
return (Executable) ReflectionUtils.getField(field, beanDefinition);
108-
}
109-
110-
return null;
108+
throw new IllegalStateException("No constructor or factory method candidate found for "
109+
+ beanDefinition + " and argument types " + valueTypes);
111110
}
112111

113112
private List<ResolvableType> determineParameterValueTypes(
@@ -390,7 +389,6 @@ private Class<?> loadClass(String beanClassName) {
390389
}
391390
}
392391

393-
@Nullable
394392
static Executable resolve(RegisteredBean registeredBean) {
395393
return new ConstructorOrFactoryMethodResolver(registeredBean.getBeanFactory())
396394
.resolve(registeredBean.getMergedBeanDefinition());

spring-beans/src/test/java/org/springframework/beans/factory/aot/ConstructorOrFactoryMethodResolverTests.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ void beanDefinitionWithMultiArgConstructorAndNonMatchingValue() {
200200
BeanDefinition beanDefinition = BeanDefinitionBuilder
201201
.rootBeanDefinition(MultiConstructorSample.class)
202202
.addConstructorArgValue(Locale.ENGLISH).getBeanDefinition();
203-
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
204-
assertThat(executable).isNull();
203+
assertThatIllegalStateException().isThrownBy(() -> resolve(new DefaultListableBeanFactory(), beanDefinition))
204+
.withMessageContaining(MultiConstructorSample.class.getName())
205+
.withMessageContaining("and argument types [java.util.Locale]");
205206
}
206207

207208
@Test
@@ -212,8 +213,9 @@ void beanDefinitionWithMultiArgConstructorAndNonMatchingValueAsInnerBean() {
212213
.rootBeanDefinition(Locale.class, "getDefault")
213214
.getBeanDefinition())
214215
.getBeanDefinition();
215-
Executable executable = resolve(new DefaultListableBeanFactory(), beanDefinition);
216-
assertThat(executable).isNull();
216+
assertThatIllegalStateException().isThrownBy(() -> resolve(new DefaultListableBeanFactory(), beanDefinition))
217+
.withMessageContaining(MultiConstructorSample.class.getName())
218+
.withMessageContaining("and argument types [java.util.Locale]");
217219
}
218220

219221
@Test
@@ -338,7 +340,6 @@ void beanDefinitionWithFactoryWithOverloadedClassMethodsOnInterface() {
338340
}
339341

340342

341-
@Nullable
342343
private Executable resolve(DefaultListableBeanFactory beanFactory, BeanDefinition beanDefinition) {
343344
return new ConstructorOrFactoryMethodResolver(beanFactory).resolve(beanDefinition);
344345
}

0 commit comments

Comments
 (0)