Java 內省(Introspector)和 BeanUtils
概述
內省(Introspector) 是Java 語言對 JavaBean 類屬性、事件的一種預設處理方法。
JavaBean是一種特殊的類,主要用於傳遞資料資訊,這種類中的方法主要用於訪問私有的欄位,且方法名符合某種命名規則。如果在兩個模組之間傳遞資訊,可以將資訊封裝進JavaBean中,這種物件稱為“值物件”(Value Object),或“VO”。方法比較少。這些資訊儲存在類的私有變數中,通過set()、get()獲得。例如UserInfo
package com.niocoder.test.introspector; /** * */ public class UserInfo { private String userName; private Integer age; private String webSite; public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getWebSite() { return webSite; } public void setWebSite(String webSite) { this.webSite = webSite; } }
在類UserInfo中有屬性 userName, 那我們可以通過 getUserName,setUserName來得到其值或者設定新的值。通過 getUserName/setUserName來訪問 userName屬性,這就是預設的規則。 Java JDK中提供了一套 API 用來訪問某個屬性的 getter/setter 方法,這就是內省。
JDK內省類庫:
PropertyDescriptor
PropertyDescriptor類表示JavaBean類通過儲存器匯出一個屬性。主要方法:
- getPropertyType(),獲得屬性的Class物件;
- getReadMethod(),獲得用於讀取屬性值的方法;getWriteMethod(),獲得用於寫入屬性值的方法;
- hashCode(),獲取物件的雜湊值;
- setReadMethod(Method readMethod),設定用於讀取屬性值的方法;
- setWriteMethod(Method writeMethod),設定用於寫入屬性值的方法。
package com.niocoder.test.introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; public class BeanInfoUtil { /** * @param userInfo 例項 * @param propertyName 屬性名 * @throws Exception */ public static void setProperty(UserInfo userInfo, String propertyName) throws Exception { PropertyDescriptor propDesc = new PropertyDescriptor(propertyName, UserInfo.class); Method methodSetUserName = propDesc.getWriteMethod(); methodSetUserName.invoke(userInfo, "鄭龍飛"); System.out.println("set userName:" + userInfo.getUserName()); } /** * @param userInfo 例項 * @param propertyName 屬性名 * @throws Exception */ public static void getProperty(UserInfo userInfo, String propertyName) throws Exception { PropertyDescriptor proDescriptor = new PropertyDescriptor(propertyName, UserInfo.class); Method methodGetUserName = proDescriptor.getReadMethod(); Object objUserName = methodGetUserName.invoke(userInfo); System.out.println("get userName:" + objUserName.toString()); } }
Introspector
將JavaBean中的屬性封裝起來進行操作。在程式把一個類當做JavaBean來看,就是呼叫Introspector.getBeanInfo()方法,得到的BeanInfo物件封裝了把這個類當做JavaBean看的結果資訊,即屬性的資訊。
getPropertyDescriptors(),獲得屬性的描述,可以採用遍歷BeanInfo的方法,來查詢、設定類的屬性。具體程式碼如下:
package com.niocoder.test.introspector; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; public class BeanInfoUtil { /** * @param userInfo 例項 * @param propertyName 屬性名 * @throws Exception */ public static void setPropertyByIntrospector(UserInfo userInfo, String propertyName) throws Exception { BeanInfo beanInfo = Introspector.getBeanInfo(UserInfo.class); PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors(); if (proDescrtptors != null && proDescrtptors.length > 0) { for (PropertyDescriptor propDesc : proDescrtptors) { if (propDesc.getName().equals(propertyName)) { Method methodSetUserName = propDesc.getWriteMethod(); methodSetUserName.invoke(userInfo, "niocoder"); System.out.println("set userName:" + userInfo.getUserName()); break; } } } } /** * @param userInfo 例項 * @param propertyName 屬性名 * @throws Exception */ public static void getPropertyByIntrospector(UserInfo userInfo, String propertyName) throws Exception { BeanInfo beanInfo = Introspector.getBeanInfo(UserInfo.class); PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors(); if (proDescrtptors != null && proDescrtptors.length > 0) { for (PropertyDescriptor propDesc : proDescrtptors) { if (propDesc.getName().equals(propertyName)) { Method methodGetUserName = propDesc.getReadMethod(); Object objUserName = methodGetUserName.invoke(userInfo); System.out.println("get userName:" + objUserName.toString()); break; } } } } }
通過這兩個類的比較可以看出,都是需要獲得PropertyDescriptor,只是方式不一樣:前者通過建立物件直接獲得,後者需要遍歷,所以使用PropertyDescriptor類更加方便。
Test
BeanInfoTest
public class BeanInfoTest { public static void main(String[] args) { UserInfo userInfo = new UserInfo(); userInfo.setUserName("merryyou"); try { BeanInfoUtil.getProperty(userInfo, "userName"); BeanInfoUtil.setProperty(userInfo, "userName"); BeanInfoUtil.getProperty(userInfo, "userName"); BeanInfoUtil.setPropertyByIntrospector(userInfo, "userName"); BeanInfoUtil.getPropertyByIntrospector(userInfo, "userName"); BeanInfoUtil.setProperty(userInfo, "age"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
輸出
get userName:merryyou set userName:鄭龍飛 get userName:鄭龍飛 set userName:niocoder get userName:niocoder Disconnected from the target VM, address: '127.0.0.1:65243', transport: 'socket' java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.niocoder.test.introspector.BeanInfoUtil.setProperty(BeanInfoUtil.java:13) at com.niocoder.test.introspector.BeanInfoTest.main(BeanInfoTest.java:19)
說明:BeanInfoUtil.setProperty(userInfo, "age")
;報錯是應為age屬性是int資料型別,而setProperty方法裡面預設給age屬性賦的值是String型別。所以會爆出argument type mismatch引數型別不匹配的錯誤資訊。
BeanUtils
由上述可看出,內省操作非常的繁瑣,所以所以Apache開發了一套簡單、易用的API來操作Bean的屬性——BeanUtils工具包。
- BeanUtils.getProperty(Object bean, String propertyName)
- BeanUtils.setProperty(Object bean, String propertyName, Object value)
BeanUtilTest
public class BeanUtilTest { public static void main(String[] args) { UserInfo userInfo = new UserInfo(); try { BeanUtils.setProperty(userInfo, "userName", "鄭龍飛"); System.out.println("set userName:" + userInfo.getUserName()); System.out.println("get userName:" + BeanUtils.getProperty(userInfo, "userName")); BeanUtils.setProperty(userInfo, "age", 18); System.out.println("set age:" + userInfo.getAge()); System.out.println("get age:" + BeanUtils.getProperty(userInfo, "age")); System.out.println("get userName type:" + BeanUtils.getProperty(userInfo, "userName").getClass().getName()); System.out.println("get age type:" + BeanUtils.getProperty(userInfo, "age").getClass().getName()); PropertyUtils.setProperty(userInfo, "age", 8); System.out.println(PropertyUtils.getProperty(userInfo, "age")); System.out.println(PropertyUtils.getProperty(userInfo, "age").getClass().getName()); // 特殊 age屬性為Integer型別 PropertyUtils.setProperty(userInfo, "age", "8"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
輸出
set userName:鄭龍飛 get userName:鄭龍飛 set age:18 get age:18 get userName type:java.lang.String get age type:java.lang.String 8 java.lang.Integer Disconnected from the target VM, address: '127.0.0.1:50244', transport: 'socket' Exception in thread "main" java.lang.IllegalArgumentException: Cannot invoke com.niocoder.test.introspector.UserInfo.setAge on bean class 'class com.niocoder.test.introspector.UserInfo' - argument type mismatch - had objects of type "java.lang.String" but expected signature "java.lang.Integer" at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2195) at org.apache.commons.beanutils.PropertyUtilsBean.setSimpleProperty(PropertyUtilsBean.java:2108) at org.apache.commons.beanutils.PropertyUtilsBean.setNestedProperty(PropertyUtilsBean.java:1914) at org.apache.commons.beanutils.PropertyUtilsBean.setProperty(PropertyUtilsBean.java:2021) at org.apache.commons.beanutils.PropertyUtils.setProperty(PropertyUtils.java:896) at com.niocoder.test.introspector.BeanUtilTest.main(BeanUtilTest.java:28) Caused by: java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2127) ... 5 more Process finished with exit code 1