本文首發於本博客 貓叔的博客,轉載請申明出處
前言
感謝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貓說
現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不定期乾貨。