diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java index 3734469d1f..0ed6fecc29 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java @@ -16,7 +16,6 @@ package org.springframework.boot.context.properties.bind; -import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,8 +34,9 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.beans.BeanUtils; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperties; +import org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperty; import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinReflectionParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; @@ -45,7 +45,6 @@ import org.springframework.core.ResolvableType; import org.springframework.core.StandardReflectionParameterNameDiscoverer; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** @@ -131,7 +130,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { private final Constructor bindConstructor; - private final PropertyDescriptor[] propertyDescriptors; + private final BeanProperties bean; private final Set> seen; @@ -145,7 +144,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { Set> compiledWithoutParameters) { this.type = type; this.bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(Bindable.of(type), nestedType); - this.propertyDescriptors = BeanUtils.getPropertyDescriptors(type); + this.bean = JavaBeanBinder.BeanProperties.of(Bindable.of(type)); this.seen = seen; this.compiledWithoutParameters = compiledWithoutParameters; } @@ -159,7 +158,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { if (this.bindConstructor != null) { handleValueObjectProperties(hints); } - else if (!ObjectUtils.isEmpty(this.propertyDescriptors)) { + else if (this.bean != null && !this.bean.getProperties().isEmpty()) { handleJavaBeanProperties(hints); } } @@ -201,33 +200,18 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { } private void handleJavaBeanProperties(ReflectionHints hints) { - for (PropertyDescriptor propertyDescriptor : this.propertyDescriptors) { - Method writeMethod = propertyDescriptor.getWriteMethod(); - if (writeMethod != null) { - hints.registerMethod(writeMethod, ExecutableMode.INVOKE); + Map properties = this.bean.getProperties(); + properties.forEach((name, property) -> { + Method getter = property.getGetter(); + if (getter != null) { + hints.registerMethod(getter, ExecutableMode.INVOKE); } - Method readMethod = propertyDescriptor.getReadMethod(); - if (readMethod != null) { - ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type); - String propertyName = propertyDescriptor.getName(); - if (isSetterMandatory(propertyName, propertyType) && writeMethod == null) { - continue; - } - handleProperty(hints, propertyName, propertyType); - hints.registerMethod(readMethod, ExecutableMode.INVOKE); + Method setter = property.getSetter(); + if (setter != null) { + hints.registerMethod(setter, ExecutableMode.INVOKE); } - } - } - - private boolean isSetterMandatory(String propertyName, ResolvableType propertyType) { - Class propertyClass = propertyType.resolve(); - if (propertyClass == null) { - return true; - } - if (isContainer(propertyType)) { - return false; - } - return !isNestedType(propertyName, propertyClass); + handleProperty(hints, name, property.getType()); + }); } private void handleProperty(ReflectionHints hints, String propertyName, ResolvableType propertyType) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java index 97e21324df..6ef232fa84 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java @@ -110,21 +110,17 @@ class JavaBeanBinder implements DataObjectBinder { } /** - * The bean being bound. - * - * @param the bean type + * The properties of a bean that may be bound. */ - static class Bean { + static class BeanProperties { - private static Bean cached; + private final Map properties = new LinkedHashMap<>(); private final ResolvableType type; private final Class resolvedType; - private final Map properties = new LinkedHashMap<>(); - - Bean(ResolvableType type, Class resolvedType) { + BeanProperties(ResolvableType type, Class resolvedType) { this.type = type; this.resolvedType = resolvedType; addProperties(resolvedType); @@ -202,10 +198,39 @@ class JavaBeanBinder implements DataObjectBinder { } } - Map getProperties() { + protected final ResolvableType getType() { + return this.type; + } + + protected final Class getResolvedType() { + return this.resolvedType; + } + + final Map getProperties() { return this.properties; } + static BeanProperties of(Bindable bindable) { + ResolvableType type = bindable.getType(); + Class resolvedType = type.resolve(Object.class); + return new BeanProperties(type, resolvedType); + } + + } + + /** + * The bean being bound. + * + * @param the bean type + */ + static class Bean extends BeanProperties { + + private static Bean cached; + + Bean(ResolvableType type, Class resolvedType) { + super(type, resolvedType); + } + @SuppressWarnings("unchecked") BeanSupplier getSupplier(Bindable target) { return new BeanSupplier<>(() -> { @@ -214,7 +239,7 @@ class JavaBeanBinder implements DataObjectBinder { instance = target.getValue().get(); } if (instance == null) { - instance = (T) BeanUtils.instantiateClass(this.resolvedType); + instance = (T) BeanUtils.instantiateClass(getResolvedType()); } return instance; }); @@ -255,10 +280,10 @@ class JavaBeanBinder implements DataObjectBinder { } private boolean isOfType(ResolvableType type, Class resolvedType) { - if (this.type.hasGenerics() || type.hasGenerics()) { - return this.type.equals(type); + if (getType().hasGenerics() || type.hasGenerics()) { + return getType().equals(type); } - return this.resolvedType != null && this.resolvedType.equals(resolvedType); + return getResolvedType() != null && getResolvedType().equals(resolvedType); } } @@ -376,6 +401,14 @@ class JavaBeanBinder implements DataObjectBinder { } } + Method getGetter() { + return this.getter; + } + + Method getSetter() { + return this.setter; + } + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java index f8ca6d3d20..d0aed438f2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java @@ -235,6 +235,14 @@ class BindableRuntimeHintsRegistrarTests { .accepts(runtimeHints); } + @Test + void registerHintsWhenHasPackagePrivateGettersAndSetters() { + RuntimeHints runtimeHints = registerHints(PackagePrivateGettersAndSetters.class); + assertThat(runtimeHints.reflection().typeHints()).singleElement() + .satisfies(javaBeanBinding(PackagePrivateGettersAndSetters.class, "getAlpha", "setAlpha", "getBravo", + "setBravo")); + } + private Consumer javaBeanBinding(Class type, String... expectedMethods) { return javaBeanBinding(type, type.getDeclaredConstructors()[0], expectedMethods); } @@ -458,6 +466,30 @@ class BindableRuntimeHintsRegistrarTests { } + public static class PackagePrivateGettersAndSetters { + + private String alpha; + + private Map bravo; + + String getAlpha() { + return this.alpha; + } + + void setAlpha(String alpha) { + this.alpha = alpha; + } + + Map getBravo() { + return this.bravo; + } + + void setBravo(Map bravo) { + this.bravo = bravo; + } + + } + public static class Address { }