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:
@@ -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.
|
||||
|
||||
29
src/test/java/org/springframework/data/util/Animal.java
Normal file
29
src/test/java/org/springframework/data/util/Animal.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.util;
|
||||
|
||||
public class Animal {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.util
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.BeanUtils
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.domain.Persistable
|
||||
import org.springframework.data.repository.Repository
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
@@ -95,9 +97,11 @@ class KotlinBeanInfoFactoryUnitTests {
|
||||
@Test // GH-2994
|
||||
internal fun includesPropertiesFromJavaSupertypes() {
|
||||
|
||||
val pds = BeanUtils.getPropertyDescriptors(MyRepositoryFactoryBeanImpl::class.java)
|
||||
val pds =
|
||||
BeanUtils.getPropertyDescriptors(MyRepositoryFactoryBeanImpl::class.java)
|
||||
|
||||
assertThat(pds).extracting("name").contains("myQueryLookupStrategyKey", "repositoryBaseClass")
|
||||
assertThat(pds).extracting("name")
|
||||
.contains("myQueryLookupStrategyKey", "repositoryBaseClass")
|
||||
}
|
||||
|
||||
@Test // GH-2993
|
||||
@@ -110,6 +114,22 @@ class KotlinBeanInfoFactoryUnitTests {
|
||||
assertThat(pds[0].readMethod).isNotNull()
|
||||
}
|
||||
|
||||
@Test // GH-3140
|
||||
internal fun specializesBeanMethods() {
|
||||
|
||||
var pds = BeanUtils.getPropertyDescriptors(Entity::class.java)
|
||||
|
||||
assertThat(pds.find { it.name == "id" }!!.readMethod!!.declaringClass).isEqualTo(
|
||||
Entity::class.java
|
||||
)
|
||||
|
||||
pds = BeanUtils.getPropertyDescriptors(DogEntity::class.java)
|
||||
|
||||
assertThat(pds.find { it.name == "name" }!!.readMethod!!.declaringClass).isEqualTo(
|
||||
DogEntity::class.java
|
||||
)
|
||||
}
|
||||
|
||||
data class SimpleDataClass(val id: String, var name: String)
|
||||
|
||||
@JvmInline
|
||||
@@ -127,7 +147,8 @@ class KotlinBeanInfoFactoryUnitTests {
|
||||
Foo, Bar
|
||||
}
|
||||
|
||||
class MyRepositoryFactoryBeanImpl<R, E, I>(repository: Class<R>) : RepositoryFactoryBeanSupport<R, E, I>(repository)
|
||||
class MyRepositoryFactoryBeanImpl<R, E, I>(repository: Class<R>) :
|
||||
RepositoryFactoryBeanSupport<R, E, I>(repository)
|
||||
where R : Repository<E, I>, E : Any, I : Any {
|
||||
|
||||
private var myQueryLookupStrategyKey: String
|
||||
@@ -149,4 +170,23 @@ class KotlinBeanInfoFactoryUnitTests {
|
||||
override var end: Long = -1L
|
||||
protected set
|
||||
}
|
||||
|
||||
class Entity(
|
||||
private val id: Long? = null,
|
||||
|
||||
val name: String
|
||||
) : Persistable<Long> {
|
||||
|
||||
override fun getId(): Long? = id
|
||||
|
||||
override fun isNew(): Boolean = id == null
|
||||
}
|
||||
|
||||
open class DogEntity : Animal() {
|
||||
|
||||
@Id
|
||||
override fun getName(): String {
|
||||
return super.getName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user