java PropertyDescriptor 应用及源码分析
1. 概述
PropertyDescriptor描述Java Bean中通过一对存储器方法(getter / setter)导出的一个属性。我们可以通过该PropertyDescriptor对bean中的该属性进行读取和写入操作,也可以设置其getter / setter。
2. 关键接口及内部属性
public PropertyDescriptor(String name, Class<?> beanClass) throws IntrospectionException
public PropertyDescriptor(String name, Class<?> beanClass, String getMethodName, String setMethodName) throws IntrospectionException
public PropertyDescriptor(String name, Method readMethod, Method writeMethod) throws IntrospectionException
public Class<?> getPropertyType()
public Method getReadMethod()
public Method getWriteMethod()
public void setReadMethod(Method readMethod) throws IntrospectionException
public void setWriteMethod(Method writeMethod)
public boolean equals(Object o)
相关的PropertyDescriptor内部属性如下:
Class<?> propertyType; //该属性的类型
Method getMethod; //getter
Method setMethod; //setter
还有继承自其父类FeatureDescriptor的功能,用于指定该属性的编程名称
3. 简单应用
现有Person类如下:
package com.cwind.property;
public class Person {
private String name ;
private int age ;
public Person(){ this.name = ""; this.age = 0; }
public Person(String name, int age) { super(); this.name = name; this. age = age; }
public String getName() { return name; }
public void setName(String name) { this. name = name; }
public int getAge() { return age; }
public void setAge(int age) { this. age = age; }
public String getNameInUpperCase(){
return this .name .toUpperCase();
}
public void setNameToLowerCase(String name){
this.name = name.toLowerCase();
}
}
该类中除了name和age两个属性的标准getter和setter之外,还有增加了一个获取大写name的get方法和一个将name设置为小写的set方法。
在测试类中,首先获得这两个方法对象。
Class personClass = Class.forName("com.cwind.property.Person");
Method read = personClass.getMethod("getNameInUpperCase", null);
Method write = personClass.getMethod("setNameToLowerCase", String.class );
//然后可以通过两种方式构造PropertyDescriptor
PropertyDescriptor prop1 = new PropertyDescriptor( "name", Person.class ); //使用其标准getter和setter
PropertyDescriptor prop2 = new PropertyDescriptor( "name", read, write); //使用read和write两个方法对象所自定义的getter和setter
//下面构建一个Person对象
Person person = new Person("Kobe" , 36);
System. out.println(prop1.getReadMethod().invoke(person, null)); // --实际调用Person.getName(), result: Kobe
System. out.println(prop2.getReadMethod().invoke(person, null)); // --实际调用Person.getNameInUpperCase(), result: KOBE
prop1.getWriteMethod().invoke(person, "James"); // --实际调用Person.setName(), person.name被设置为James
prop2.getWriteMethod().invoke(person, "James"); // --实际调用Person.setNameToLowerCase(), person.name被设置为james
4. 源码分析
构造函数1:
public PropertyDescriptor(String name, Class<?> beanClass)
throws IntrospectionException {
setName(name); //设置属性编程名,本例中即'name'
if (name.length() == 0){
throw new IntrospectionException("empty property name");
// 编程名为空则抛出异常
}
String caps = Character.toUpperCase(name.charAt(0)) + name.substring(1);
// 标准getter应为getName()或isName(), 先将首字母大写
findMethods(beanClass, "is" + caps, "get" + caps, "set" + caps);
// 参数依次为:类类型,可能的getter函数名1,可能的getter函数名2,setter函数名
if (getMethod == null){ // findMethods()设置PropertyDescriptor的getMethod和setMethod属性
throw new IntrospectionException(
"Cannot find a is" + caps + " or get" + caps + " method");
}
if (setMethod == null){
throw new IntrospectionException(
"Cannot find a " + caps + " method" );
}
propertyType = checkMethods(getMethod, setMethod);
// checkMethods()函数用来检测getMethod得到的类型与setMethod的参数类型是否匹配,若匹配则置propertyType为该类型
}
构造函数2:
public PropertyDescriptor(String name, Class
private void findMethods(Class beanClass, String getMethodName1, String getMethodName2, String setMethodName) throws IntrospectionException {
try {
// 首先查找getMethodName1指定的getter (isXXX)
if (getMethodName1 != null) {
try {
getMethod = beanClass.getMethod(getMethodName1, new Class[0]);
}
catch (NoSuchMethodException e)
{}
}
// 若失败,则查找getMethodName2指定的getter (getXXX)
if (getMethod == null && getMethodName2 != null) {
try {
getMethod = beanClass.getMethod(getMethodName2, new Class[0]);
}
catch (NoSuchMethodException e)
{}
}
if (setMethodName != null) {
if (getMethod != null) {
// 如果得到了getMethod,则通过其返回值类型决定setMethod的参数类型
Class propertyType = getMethod.getReturnType();
if (propertyType == Void.TYPE) {
// 若getter的返回值为Void类型则抛出异常
String msg = "The property's read method has return type 'void'";
throw new IntrospectionException(msg);
}
Class[] setArgs = new Class[] { propertyType };
try {
setMethod = beanClass.getMethod(setMethodName, setArgs);
// 通过函数名和参数类型获得setMethod
}
catch (NoSuchMethodException e)
{}
}
else if (getMethodName1 == null && getMethodName2 == null) {
// getMethodName1和2均为空,则此属性为只写属性,此时遍历bean中的函数,返回第一个名称与setMethodName一致且返回类型为Void的单参数函数
Method[] methods = beanClass.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(setMethodName)
&& methods[i].getParameterTypes().length == 1
&& methods[i].getReturnType() == Void.TYPE) {
setMethod = methods[i];
break;
}
}
}
}
}
catch (SecurityException e) {
String msg = "SecurityException thrown on attempt to access methods."; // 作者在纠结要不要修改异常类型
throw new IntrospectionException(msg);
}
}
checkMethods方法
private Class<?> checkMethods(Method readMethod, Method writeMethod) throws IntrospectionException {
Class<?> newPropertyType = propertyType;
// 合法的read方法应该无参同时带有一个非空的返回值类型
if (readMethod != null) {
if (readMethod.getParameterTypes().length > 0) {
throw new IntrospectionException("read method has unexpected parameters");
}
newPropertyType = readMethod.getReturnType();
if (newPropertyType == Void.TYPE) {
throw new IntrospectionException("read method return type is void");
}
}
// 合法的write方法应该包含一个类型相同的参数
if (writeMethod != null) {
if (writeMethod.getParameterTypes().length != 1) { // 参数不能超过一个
String msg = "write method does not have exactly one parameter" ;
throw new IntrospectionException(msg);
}
if (readMethod == null) {
// 若无read方法,属性类型就应为writeMethod的参数类型
newPropertyType = writeMethod.getParameterTypes()[0];
}
else {
// 检查read方法的返回值类型是否与write方法的参数类型相匹配
if (newPropertyType != null
&& !newPropertyType.isAssignableFrom(
writeMethod.getParameterTypes()[0])) {
throw new IntrospectionException("read and write method are not compatible");
}
}
}
return newPropertyType;
}
最后提一句PropertyDescriptor.equals(), 只有当属性类型、标志、读写方法和PropertyEditorClass均相同时才认为两个PropertyDescriptor相等
return samePropertyType
&& sameFlags
&& sameReadMethod
&& sameWriteMethod
&& samePropertyEditorClass;