Consider user classes when finding bind constructor

Update `@Autowired` detection logic to consider all constructors and to
search user classes.

Fixes gh-33061

Co-authored-by: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
Andy Wilkinson 2022-11-10 14:20:25 +00:00 committed by Phillip Webb
parent d6ecdd5741
commit a2f65cd887
2 changed files with 36 additions and 11 deletions

View File

@ -19,12 +19,15 @@ package org.springframework.boot.context.properties.bind;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.stream.Stream;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Default {@link BindConstructorProvider} implementation.
@ -75,9 +78,9 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
}
static Constructors getConstructors(Class<?> type) {
boolean hasAutowiredConstructor = isAutowiredPresent(type);
Constructor<?>[] candidates = getCandidateConstructors(type);
MergedAnnotations[] candidateAnnotations = getAnnotations(candidates);
boolean hasAutowiredConstructor = isAutowiredPresent(candidateAnnotations);
Constructor<?> bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations);
if (bind == null && !hasAutowiredConstructor) {
bind = deduceBindConstructor(type, candidates);
@ -88,6 +91,15 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
return new Constructors(hasAutowiredConstructor, bind);
}
private static boolean isAutowiredPresent(Class<?> type) {
if (Stream.of(type.getDeclaredConstructors()).map(MergedAnnotations::from)
.anyMatch((annotations) -> annotations.isPresent(Autowired.class))) {
return true;
}
Class<?> userClass = ClassUtils.getUserClass(type);
return (userClass != type) ? isAutowiredPresent(userClass) : false;
}
private static Constructor<?>[] getCandidateConstructors(Class<?> type) {
if (isInnerClass(type)) {
return new Constructor<?>[0];
@ -112,20 +124,11 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
private static MergedAnnotations[] getAnnotations(Constructor<?>[] candidates) {
MergedAnnotations[] candidateAnnotations = new MergedAnnotations[candidates.length];
for (int i = 0; i < candidates.length; i++) {
candidateAnnotations[i] = MergedAnnotations.from(candidates[i]);
candidateAnnotations[i] = MergedAnnotations.from(candidates[i], SearchStrategy.SUPERCLASS);
}
return candidateAnnotations;
}
private static boolean isAutowiredPresent(MergedAnnotations[] candidateAnnotations) {
for (MergedAnnotations annotations : candidateAnnotations) {
if (annotations.isPresent(Autowired.class)) {
return true;
}
}
return false;
}
private static Constructor<?> getConstructorBindingAnnotated(Class<?> type, Constructor<?>[] candidates,
MergedAnnotations[] mergedAnnotations) {
Constructor<?> result = null;

View File

@ -21,6 +21,9 @@ import java.lang.reflect.Constructor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@ -97,6 +100,16 @@ class DefaultBindConstructorProviderTests {
assertThat(constructor).isNull();
}
@Test
void getBindConstructorFromProxiedClassWithOneAutowiredConstructorReturnsNull() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
ProxiedWithOneConstructorWithAutowired.class)) {
ProxiedWithOneConstructorWithAutowired bean = context.getBean(ProxiedWithOneConstructorWithAutowired.class);
Constructor<?> bindConstructor = this.provider.getBindConstructor(bean.getClass(), false);
assertThat(bindConstructor).isNull();
}
}
static class OnlyDefaultConstructor {
}
@ -188,4 +201,13 @@ class DefaultBindConstructorProviderTests {
}
@Configuration
static class ProxiedWithOneConstructorWithAutowired {
@Autowired
ProxiedWithOneConstructorWithAutowired(Environment environment) {
}
}
}