From a2f65cd887b4c122094de13ef883269823359ff8 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 10 Nov 2022 14:20:25 +0000 Subject: [PATCH] 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 --- .../bind/DefaultBindConstructorProvider.java | 25 +++++++++++-------- .../DefaultBindConstructorProviderTests.java | 22 ++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java index c9bdb2755ba..ee67057f069 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java @@ -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; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java index dbe76bb34d1..7bd7121b345 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java @@ -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) { + } + + } + }