-
Notifications
You must be signed in to change notification settings - Fork 41.5k
Description
Summary
If a bean class contains multiple getter/setter methods with a same name but different types, the binding mechanism does not sort these methods properly which may lead to a non-deterministic/erroneous behaviour.
Affects v2.7.5 (current main).
Details
Let's have following bean classes:
static class Child extends Parent<ChildProperty> {
@Override
public ChildProperty getProperty() {
return null;
}
}
abstract static class Parent<T extends ParentProperty> {
abstract public T getProperty();
}
static class ChildProperty extends ParentProperty {
}
abstract static class ParentProperty {
}
When compiled, the Child
class does have two getter methods. One from the Parent
class with the ParentProperty
return type, another from it's own implementation with the ChildPropertyReturnType
.
$ javap Child.class
Compiled from "Child.java"
class Child extends Parent<ChildProperty> {
Child();
public ChildProperty getProperty();
public ParentProperty getProperty();
}
The binding mechanism in the JavaBeanBinder.Bean.addProperties()
method, reads all declared methods via reflection and then tries to sort those methods by its name. Reason of sorting is non-determinism of the Class.getDeclaredMethods()
reflection method. This issue was previously noted in #24068
The problems is: Sorting is solely based on method name which does not (fully) remove the aforementioned non-determinism.
Additional details
- I've encountered this issue in Kotlin, where constructs like
abstract var property: T
are slightly more common. - Here is a test app FieldsOrderingTest.txt (please rename the
txt
extension tojava
) - Maybe unexpectedly Java compiler does not flag the
public ParentProperty getProperty()
on theChild
class as abstract. The following code returns array of false values:This means theObject[] abstracts = Arrays.stream(Child.class.getDeclaredMethods()) .map(Method::getModifiers) .map(Modifier::isAbstract) .toArray(); // abstracts = {Object[2]@710} // 0 = {Boolean@712} false // 1 = {Boolean@712} false
JavaBeanBinder.Bean.isCandidate()
method returns true for bothgetProperty()
methods in theChild
class.