Specialize Kotlin property accessors in KotlinBeanInfoFactory.

We now attempt to detect property accessors for properties declared in Kotlin that do not have a Kotlin-style accessor but one that instead comes from an interface.

Also, we specialize accessor methods that are inherited from a Java superclass but override accessors in the Kotlin realm.

Closes #3140
Closes #3146
This commit is contained in:
Mark Paluch
2024-09-02 14:36:05 +02:00
parent 3e75581336
commit b99db0cf01
3 changed files with 111 additions and 10 deletions

View File

@@ -29,15 +29,19 @@ import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.BeanInfoFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link BeanInfoFactory} specific to Kotlin types using Kotlin reflection to determine bean properties.
@@ -62,7 +66,7 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered {
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(beanClass);
Collection<KCallable<?>> members = kotlinClass.getMembers();
Set<PropertyDescriptor> pds = new LinkedHashSet<>(members.size());
Map<String, PropertyDescriptor> descriptors = new LinkedHashMap<>(members.size(), 1.f);
for (KCallable<?> member : members) {
@@ -71,6 +75,16 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered {
Method getter = ReflectJvmMapping.getJavaGetter(property);
Method setter = property instanceof KMutableProperty<?> kmp ? ReflectJvmMapping.getJavaSetter(kmp) : null;
if (getter == null) {
Type javaType = ReflectJvmMapping.getJavaType(property.getReturnType());
getter = ReflectionUtils.findMethod(beanClass,
javaType == Boolean.TYPE ? "is" : "get" + StringUtils.capitalize(property.getName()));
}
if (getter != null) {
getter = ClassUtils.getMostSpecificMethod(getter, beanClass);
}
if (getter != null && (Modifier.isStatic(getter.getModifiers()) || getter.getParameterCount() != 0)) {
continue;
}
@@ -82,7 +96,7 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered {
}
}
pds.add(new PropertyDescriptor(property.getName(), getter, setter));
descriptors.put(property.getName(), new PropertyDescriptor(property.getName(), getter, setter));
}
}
@@ -95,9 +109,17 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered {
if (javaClass != Object.class) {
PropertyDescriptor[] javaPropertyDescriptors = BeanUtils.getPropertyDescriptors(javaClass);
pds.addAll(Arrays.asList(javaPropertyDescriptors));
for (PropertyDescriptor descriptor : javaPropertyDescriptors) {
descriptor = new PropertyDescriptor(descriptor.getName(), specialize(beanClass, descriptor.getReadMethod()),
specialize(beanClass, descriptor.getWriteMethod()));
descriptors.put(descriptor.getName(), descriptor);
}
}
PropertyDescriptor[] propertyDescriptors = descriptors.values().toArray(new PropertyDescriptor[0]);
return new SimpleBeanInfo() {
@Override
public BeanDescriptor getBeanDescriptor() {
@@ -106,11 +128,21 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered {
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return pds.toArray(new PropertyDescriptor[0]);
return propertyDescriptors;
}
};
}
@Nullable
private static Method specialize(Class<?> beanClass, @Nullable Method method) {
if (method == null) {
return method;
}
return ClassUtils.getMostSpecificMethod(method, beanClass);
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE - 10; // leave some space for customizations.