關於屬性描述符PropertyDescriptor

本文首發於本博客 貓叔的博客,轉載請申明出處

前言

感謝GY丶L粉絲的提問:屬性描述器PropertyDescriptor是幹嘛用的?

本來我也沒有仔細瞭解過描述符這一塊的知識,不過粉絲問了,我就抽週末的時間看看,順便學習一下,粉絲問的剛好是PropertyDescriptor這個屬性描述符,我看了下源碼。

/**
 * A PropertyDescriptor describes one property that a Java Bean
 * exports via a pair of accessor methods.
 */
public class PropertyDescriptor extends FeatureDescriptor {
    //...
}

emmmm,假裝自己英語能厲害的說,屬性描述符描述了一個屬性,即Java Bean 通過一對訪問器方法來導出。(沒錯,他確實是存在於java.beans包下的)

描述符

通過類關係圖,可以知道,我們應該提前瞭解一下FeatureDescriptor才行了。很好,起碼目前還沒有設計抽象類或者接口。

FeatureDescriptor

/**
 * The FeatureDescriptor class is the common baseclass for PropertyDescriptor,
 * EventSetDescriptor, and MethodDescriptor, etc.
 * <p>
 * It supports some common information that can be set and retrieved for
 * any of the introspection descriptors.
 * <p>
 * In addition it provides an extension mechanism so that arbitrary
 * attribute/value pairs can be associated with a design feature.
 */

public class FeatureDescriptor {
    //...
}

okay,這是很合理的設計方式,FeatureDescriptor爲類似PropertyDescriptor、EvebtSetDescriptor、MethodDescriptor的描述符提供了一些共用的常量信息。同時它也提供一個擴展功能,方便任意屬性或鍵值對可以於設計功能相關聯。

這裏簡單的說下,在我大致看了一下源碼後(可能不夠詳細,最近有點忙,時間較趕),FeatureDescriptor主要是針對一下屬性的一些get/set,同時這些屬性都是基本通用於PropertyDescriptor、EvebtSetDescriptor、MethodDescriptor。

    private boolean expert; // 專有
    private boolean hidden; // 隱藏
    private boolean preferred; // 首選
    private String shortDescription; //簡單說明
    private String name; // 編程名稱
    private String displayName; //本地名稱
    private Hashtable<String, Object> table; // 屬性表

其實該類還有另外幾個方法,比如深奧的構造函數等等,這裏就不深入探討了。

PropertyDescriptor

那麼我們大致知道了FeatureDescriptor,接下來就可以來深入瞭解看看這個屬性描述符PropertyDescriptor

說到屬性,大家一定會想到的就是get/set這個些基礎的東西,當我打開PropertyDescriptor源碼的時候,我也看到了一開始猜想的點。

    private final MethodRef readMethodRef = new MethodRef();
    private final MethodRef writeMethodRef = new MethodRef();
    private String writeMethodName;
    private String readMethodName;

這裏的代碼是我從源碼中抽離的一部分,起碼我們這樣看可以大致理解,是分爲寫和讀的步驟,那麼就和我們初學java的get/set是一致的。

同時我還看到了,這個,及其註釋。

    // The base name of the method name which will be prefixed with the
    // read and write method. If name == "foo" then the baseName is "Foo"
    private String baseName;

這好像可以解釋,爲什麼我們的屬性在生成get/set的時候,第一個字母變成大寫?!註釋好像確實是這樣寫的。

由於可能需要一個Bean對象,所以我以前在案例中先創建了一個Cat類。

public class Cat {

    private String name;

    private String describe;

    private int age;

    private int weight;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

構造函數

起碼目前,我還不知道我應該怎麼使用它,那麼我們就一步一步來吧,我看到它有好幾個構造函數,這是一個有趣而且有難度的事情,我們先試着創建一個PropertyDescriptor吧。

  • 第一種構造函數
    /**
     * Constructs a PropertyDescriptor for a property that follows
     * the standard Java convention by having getFoo and setFoo
     * accessor methods.  Thus if the argument name is "fred", it will
     * assume that the writer method is "setFred" and the reader method
     * is "getFred" (or "isFred" for a boolean property).  Note that the
     * property name should start with a lower case character, which will
     * be capitalized in the method names.
     *
     * @param propertyName The programmatic name of the property.
     * @param beanClass The Class object for the target bean.  For
     *          example sun.beans.OurButton.class.
     * @exception IntrospectionException if an exception occurs during
     *              introspection.
     */
    public PropertyDescriptor(String propertyName, Class<?> beanClass)
                throws IntrospectionException {
        this(propertyName, beanClass,
                Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
                Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
    }

這個好像是參數最少的,它只需要我們傳入一個屬性字符串,還有對應的類就好了,其實它也是調用了另一個構造函數,只是它會幫我們默認生成讀方法和寫方法。方法中的Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName)其實就是自己拼出一個默認的get/set方法,大家有興趣可以去看看源碼。

那麼對應的實現內容,我想大家應該都想到了。

    public static void main(String[] args) throws Exception {
        PropertyDescriptor CatPropertyOfName = new PropertyDescriptor("name", Cat.class);
        System.out.println(CatPropertyOfName.getPropertyType());
        System.out.println(CatPropertyOfName.getPropertyEditorClass());
        System.out.println(CatPropertyOfName.getReadMethod());
        System.out.println(CatPropertyOfName.getWriteMethod());
    }
  • 第二種構造函數
/**
     * This constructor takes the name of a simple property, and method
     * names for reading and writing the property.
     *
     * @param propertyName The programmatic name of the property.
     * @param beanClass The Class object for the target bean.  For
     *          example sun.beans.OurButton.class.
     * @param readMethodName The name of the method used for reading the property
     *           value.  May be null if the property is write-only.
     * @param writeMethodName The name of the method used for writing the property
     *           value.  May be null if the property is read-only.
     * @exception IntrospectionException if an exception occurs during
     *              introspection.
     */
    public PropertyDescriptor(String propertyName, Class<?> beanClass,
                String readMethodName, String writeMethodName)
                throws IntrospectionException {
        if (beanClass == null) {
            throw new IntrospectionException("Target Bean class is null");
        }
        if (propertyName == null || propertyName.length() == 0) {
            throw new IntrospectionException("bad property name");
        }
        if ("".equals(readMethodName) || "".equals(writeMethodName)) {
            throw new IntrospectionException("read or write method name should not be the empty string");
        }
        setName(propertyName);
        setClass0(beanClass);

        this.readMethodName = readMethodName;
        if (readMethodName != null && getReadMethod() == null) {
            throw new IntrospectionException("Method not found: " + readMethodName);
        }
        this.writeMethodName = writeMethodName;
        if (writeMethodName != null && getWriteMethod() == null) {
            throw new IntrospectionException("Method not found: " + writeMethodName);
        }
        // If this class or one of its base classes allow PropertyChangeListener,
        // then we assume that any properties we discover are "bound".
        // See Introspector.getTargetPropertyInfo() method.
        Class[] args = { PropertyChangeListener.class };
        this.bound = null != Introspector.findMethod(beanClass, "addPropertyChangeListener", args.length, args);
    }

沒錯,這個構造函數就是第一種構造函數內部二次調用的,所需要的參數很簡單,同時我也希望大家可以借鑑這個方法中的一些檢測方式。這次的實現方式也是同樣的形式。

    public static void main(String[] args) throws Exception {
        PropertyDescriptor CatPropertyOfName = new PropertyDescriptor("name", Cat.class,"getName","setName");
        System.out.println(CatPropertyOfName.getPropertyType());
        System.out.println(CatPropertyOfName.getPropertyEditorClass());
        System.out.println(CatPropertyOfName.getReadMethod());
        System.out.println(CatPropertyOfName.getWriteMethod());
    }
  • 第三種構造函數
    /**
     * This constructor takes the name of a simple property, and Method
     * objects for reading and writing the property.
     *
     * @param propertyName The programmatic name of the property.
     * @param readMethod The method used for reading the property value.
     *          May be null if the property is write-only.
     * @param writeMethod The method used for writing the property value.
     *          May be null if the property is read-only.
     * @exception IntrospectionException if an exception occurs during
     *              introspection.
     */
    public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)
                throws IntrospectionException {
        if (propertyName == null || propertyName.length() == 0) {
            throw new IntrospectionException("bad property name");
        }
        setName(propertyName);
        setReadMethod(readMethod);
        setWriteMethod(writeMethod);
    }

這個不用傳類,因爲你需要傳遞兩個實際的方法進來,所以主要三個對應屬性的參數既可。看看大致的實現內容

    public static void main(String[] args) throws Exception {
        Class<?> classType = Cat.class;
        Method CatNameOfRead = classType.getMethod("getName");
        Method CatNameOfWrite = classType.getMethod("setName", String.class);
        PropertyDescriptor CatPropertyOfName = new PropertyDescriptor("name", CatNameOfRead,CatNameOfWrite);
        System.out.println(CatPropertyOfName.getPropertyType());
        System.out.println(CatPropertyOfName.getPropertyEditorClass());
        System.out.println(CatPropertyOfName.getReadMethod());
        System.out.println(CatPropertyOfName.getWriteMethod());
    }

好了,大致介紹了幾種構造函數與實現方式,起碼我們現在知道它需要什麼。

一些使用方式

其實在我上面寫一些構造函數的時候,我想大家應該已經感受到與反射相關了,起碼我感覺上是這樣的,所以我一開始想到這樣的案例形式,通過反射與這個屬性描述類去賦予我的類。

    public static void main(String[] args) throws Exception {
        //獲取類
        Class classType = Class.forName("com.example.demo.beans.Cat");
        Object catObj = classType.newInstance();
        //獲取Name屬性
        PropertyDescriptor catPropertyOfName = new PropertyDescriptor("name",classType);
        //得到對應的寫方法
        Method writeOfName = catPropertyOfName.getWriteMethod();
        //將值賦進這個類中
        writeOfName.invoke(catObj,"river");
        Cat cat = (Cat)catObj;
        System.out.println(cat.toString());
    }

運行結果還是順利的。

Cat{name='river', describe='null', age=0, weight=0}

可以看到,我們確實得到了一個理想中的對象。

那麼我是不是可以改變一個已經創建的對象呢?

    public static void main(String[] args) throws Exception {
        //一開始的默認對象
        Cat cat = new Cat("river","黑貓",2,4);
        //獲取name屬性
        PropertyDescriptor catPropertyOfName = new PropertyDescriptor("name",Cat.class);
        //得到讀方法
        Method readMethod = catPropertyOfName.getReadMethod();
        //獲取屬性值
        String name = (String) readMethod.invoke(cat);
        System.out.println("默認:" + name);
        //得到寫方法
        Method writeMethod = catPropertyOfName.getWriteMethod();
        //修改值
        writeMethod.invoke(cat,"copy");
        System.out.println("修改後:" + cat);
    }

上面的demo是,我先創建了一個對象,然後通過屬性描述符讀取name值,再進行修改值,最後輸出的對象的值也確實改變了。

默認:river
修改後:Cat{name='copy', describe='黑貓', age=2, weight=4}

收尾

這是一個有趣的API,我想另外兩個(EvebtSetDescriptor、MethodDescriptor)應該也差不多,大家可以再通過此方法去探究,只有自己嘗試一次才能學到這裏面的一些東西,還有一些項目場景的使用方式,不過一般的業務場景應該很少使用到這個API。那麼這個東西究竟可以幹什麼呢?我想你試着敲一次也許有一些答案了。

公衆號:Java貓說

現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不定期乾貨。

Image Text

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章