Generate hints for all methods that JavaBeanBinder may call
Fixes gh-35397
This commit is contained in:
parent
c254610e4d
commit
acafb907f6
|
@ -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<Class<?>> seen;
|
||||
|
||||
|
@ -145,7 +144,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
|||
Set<Class<?>> 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<String, BeanProperty> 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) {
|
||||
|
|
|
@ -110,21 +110,17 @@ class JavaBeanBinder implements DataObjectBinder {
|
|||
}
|
||||
|
||||
/**
|
||||
* The bean being bound.
|
||||
*
|
||||
* @param <T> the bean type
|
||||
* The properties of a bean that may be bound.
|
||||
*/
|
||||
static class Bean<T> {
|
||||
static class BeanProperties {
|
||||
|
||||
private static Bean<?> cached;
|
||||
private final Map<String, BeanProperty> properties = new LinkedHashMap<>();
|
||||
|
||||
private final ResolvableType type;
|
||||
|
||||
private final Class<?> resolvedType;
|
||||
|
||||
private final Map<String, BeanProperty> 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<String, BeanProperty> getProperties() {
|
||||
protected final ResolvableType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
protected final Class<?> getResolvedType() {
|
||||
return this.resolvedType;
|
||||
}
|
||||
|
||||
final Map<String, BeanProperty> 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 <T> the bean type
|
||||
*/
|
||||
static class Bean<T> extends BeanProperties {
|
||||
|
||||
private static Bean<?> cached;
|
||||
|
||||
Bean(ResolvableType type, Class<?> resolvedType) {
|
||||
super(type, resolvedType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
BeanSupplier<T> getSupplier(Bindable<T> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<TypeHint> 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<String, String> bravo;
|
||||
|
||||
String getAlpha() {
|
||||
return this.alpha;
|
||||
}
|
||||
|
||||
void setAlpha(String alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
Map<String, String> getBravo() {
|
||||
return this.bravo;
|
||||
}
|
||||
|
||||
void setBravo(Map<String, String> bravo) {
|
||||
this.bravo = bravo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Address {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue