Java PropertyDescriptor 应用及源码分析

本文详细阐述了JavaBean属性操作中使用的PropertyDescriptor类,包括其关键接口及内部属性,通过实例展示了如何利用该类对JavaBean属性进行读取、写入以及自定义getter和setter方法的应用。同时,提供了源码分析,揭示了构造函数和辅助函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前文  从Introspector谈Java内省机制  提到了通过Introspector.getBeanInfo()方法获取属性描述符数组,进而读取属性值的方式,但未对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<?> beanClass, String getMethodName, String setMethodName) throws IntrospectionException
 
其实现与构造函数1类似,只是调用findMethods时直接查找指定的getter和setter函数名:
        findMethods(beanClass, getMethodName, null, setMethodName);
 
构造函数3则更加直观,直接设置方法对象
public PropertyDescriptor(String name, Method readMethod, Method writeMethod) throws IntrospectionException
 
两个比较重要的私有辅助函数分别为findMethods()和checkMethods(),分别看一下其实现
 
findMethods用来按指定的getter和setter函数名在指定Class中查找getMethod和setMethod,并设置PropertyDescriptor的相关属性 
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;
 
<think>我们正在讨论Java中的PropertyDescriptor类,它属于java.beans包,用于获取和操作JavaBean属性的读写方法(getter和setter)。根据引用[1]和引用[3]的信息,我们可以通过几种不同的方式来创建PropertyDescriptor对象,并动态地设置和获取属性值。主要使用步骤:1.创建PropertyDescriptor对象:需要指定属性名和对应的JavaBean类(或者直接提供读写方法)。2.通过PropertyDescriptor对象获取读写方法(Method对象)。3.调用读写方法(invoke)来设置或获取属性值。引用[1]提供了一个完整的示例,演示了如何使用PropertyDescriptor设置和获取Person对象的name属性。下面我将详细说明几种构造方法(引用[3]的内容):1.PropertyDescriptor(StringpropertyName,Class<?>beanClass)这是最常用的构造器,通过属性名和bean的Class对象来创建。它会自动根据JavaBean规范推导出读写方法(例如,对于属性"name",读取方法为"getName"或"isName",写入方法为"setName")。2.PropertyDescriptor(StringpropertyName,Class<?>beanClass,StringreadMethodName,StringwriteMethodName)这个构造器允许你指定读写方法的名称,适用于不符合JavaBean命名规范的情况。3.PropertyDescriptor(StringpropertyName,MethodreadMethod,MethodwriteMethod)直接通过读写方法的Method对象来创建,这样更加灵活,但需要自己获取Method对象。4.PropertyDescriptor(Class<?>bean,Stringbase,Methodread,Methodwrite)这个构造器与上一个类似,但属性名会通过Introspector.decapitalize(base)来转换(比如将"Name"转换为"name")。现在,我们根据引用[1]的示例,编写一个更详细的示例,并解释每一步。示例:使用PropertyDescriptor动态操作Person对象的属性步骤:1.定义一个JavaBean(Person类,有name属性)。2.创建PropertyDescriptor对象,指定属性名为"name",类为Person.class。3.获取写入方法(setName)并调用,设置属性值。4.获取读取方法(getName)并调用,获取属性值。代码示例:注意:需要处理异常(IntrospectionException,IllegalAccessException,InvocationTargetException等)下面我们编写一个完整的示例代码:</think> ### Java PropertyDescriptor 使用方法详解 `PropertyDescriptor` 是 `java.beans` 包中的核心工具类,用于**动态操作 JavaBean 的属性**。它通过反射机制实现对属性的读写控制,特别适用于需要动态处理对象属性的场景。 #### 核心使用步骤 1. **创建 PropertyDescriptor 对象** ```java // 方式1:使用属性名和Bean类 PropertyDescriptor pd = new PropertyDescriptor("属性名", Bean类.class); // 方式2:自定义读写方法名(处理特殊命名) PropertyDescriptor pd = new PropertyDescriptor("属性名", Bean类.class, "读取方法名", "写入方法名"); // 方式3:直接指定读写方法(最灵活) Method readMethod = Bean类.class.getMethod("读取方法"); Method writeMethod = Bean类.class.getMethod("写入方法", 参数类型.class); PropertyDescriptor pd = new PropertyDescriptor("属性名", readMethod, writeMethod); ``` 2. **操作属性值** ```java // 获取写入方法并设置值 Method setter = pd.getWriteMethod(); setter.invoke(目标对象, 新值); // 获取读取方法并获取值 Method getter = pd.getWriteMethod(); 类型 结果 = (类型) getter.invoke(目标对象); ``` #### 完整示例 ```java import java.beans.PropertyDescriptor; import java.lang.reflect.Method; class Employee { private String name; // Getter public String fetchName() { return name; } // Setter public void updateName(String name) { this.name = name; } } public class Main { public static void main(String[] args) throws Exception { Employee emp = new Employee(); // 使用自定义方法名创建 PropertyDescriptor pd = new PropertyDescriptor( "name", Employee.class, "fetchName", // 自定义Getter方法名 "updateName" // 自定义Setter方法名 ); // 设置属性值 Method setter = pd.getWriteMethod(); setter.invoke(emp, "张三"); // 读取属性值 Method getter = pd.getReadMethod(); String name = (String) getter.invoke(emp); System.out.println("员工姓名: " + name); // 输出:员工姓名: 张三 } } ``` #### 注意事项 1. **命名规范** - 默认查找规则:`get`/`set + 属性名(首字母大写)` - boolean属性:可以使用 `is + 属性名` 作为getter[^3] 2. **错误处理** ```java try { // 属性操作代码 } catch (IntrospectionException e) { // 处理属性不存在/方法不匹配 } catch (IllegalAccessException | InvocationTargetException e) { // 处理反射调用异常 } ``` 3. **性能优化** - 频繁使用时缓存 `PropertyDescriptor` 对象 - 优先使用构造函数`PropertyDescriptor(String, Class, String, String)`避免多次反射查找[^2] ### 典型应用场景 1. 动态表单数据绑定 2. 对象转换工具(如BeanUtils) 3. 序列化/反序列化框架 4. 基于注解的属性处理 5. IDE 的代码提示功能实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值