java.beans.PropertyDescriptor的一个小坑
JDK版本:1.8
PropertyDescriptor是java提供的一个反射工具类,可以方便的通过反射获得属性的set/get方法。
但是在下面的特殊场景下,可能会出现一个概率性报错
public interface Super {
public Number getA();
}
public class SubClass implements Super {
private Integer a;
public Integer getA() {
return this.a;
}
public void setA(Integer a) {
this.a = a;
}
}
此时使用PropertyDescriptor获取属性a的set方法:
public static void main(String[] args) throws Exception{
Method setter = new PropertyDescriptor("a", SubClass.class).getWriteMethod();
}
会大概率报错:
深入源码后发现,内置的查找setter函数的方法:java.beans.Introspector#internalFindMethod会使用getPublicDeclaredMethods()函数列出所有公共Method引用,包括继承和实现接口的方法,然后从该公共Method列表中查找对应的setter方法。
由于SubClass实现了Super接口,所以公共Method列表中会同时包含两个get方法引用:
public Number getA(); // 来自Super接口
public Integer getA(); // 来自SubClass类
Introspector的internalFindMethod方法会返回名称匹配的第一个Method引用。
由于公共Method列表顺序有时会有变化,导致查找属性的get函数时internalFindMethod方法返回的函数引用不固定,可能是Number getA(),也可能是Integer getA()。(概率性报错的来源)
问题就出在这里。查找到set方法后,PropertyDescriptor会在getReadMethod()方法内保存get方法声明的返回类型,并在getWriteMethod()方法内获取该返回类型并包装成查找WriteMethod时的args类型。
即:PropertyDescriptor查找set方法时的入参参数类型使用的是get方法声明的返回类型,而不是对应属性本身的类型。
因为SubClass本身声明的set/get方法都使用的是Integer类型,单从代码本身看是没有什么问题的,但是结合Introspector的查找逻辑就会导致查询失败。逻辑如下:
- PropertyDescriptor先解析get方法,此时可能返回两个不同的get方法引用,其声明的返回类型分别是Number和Integer
- PropertyDescriptor解析到get方法后,会将get方法的返回类型存下来,此时可能是Number或Integer
- PropertyDescriptor解析set方法,使用get方法的返回类型(Number或Integer)作为入参类型,此时如果使用的是Number作为入参类型,则找不到set方法
结论:当类声明的get方法返回类型和set方法的入参类型不一致,或者因为继承父类、实现接口导致方法覆盖、重载并且get方法会有不同返回类型时,不要使用PropertyDescriptor类来解析set方法,有概率出错。
如果只是set方法的入参类型有多种则不会出问题。即get方法(包括覆盖、重载等)的返回类型都有对应入参类型的set方法时,使用PropertyDescriptor就不会出现问题。(即get方法的返回类型的集合是set入参方法类型的集合的子集时)