java內省機制及PropertyUtils使用方法

背景html

       通常狀況下,在Java中你能夠經過get方法輕鬆獲取beans中的屬性值。可是,當你事先不知道beans的類型或者將要訪問或修改的屬性名時,該怎麼辦?Java語言中提供了一些像java.beans.Introspector這 樣類,實現了在運行時檢測Java類並肯定屬性get和set方法的名稱,結合Java中的反射機制就能夠調用這些方法了。然而,這些APIs使用起來比 較困難,而且將Java類中一些沒必要要的底層結構暴露給了開發人員。BeanUtils包中的APIs試圖簡化動態獲取和設置bean屬性的過程。java

      BeanUtils包中的PropertyUtils類中的一些靜態方法實現了上面的功能,稍後會詳細介紹。首先,介紹一些有用的定義:ios

     JavaBean支持的屬性類型通常能夠劃分紅三類--標準的JavaBeans規範支持其中的一些,也有一部分只有BeanUtils包支持:web

  • Simple(單值)  --  單值或量,有個一能夠訪問或修改的屬性。值的類型多是Java語言的原生類型(如:int型),簡單的類(如:java.lang.String),或者一個複雜類的對象,這個類可能來自Java語言或者來自應用程序再或者來自應用程序中的一個類庫。
  • Indexed(索 引)  --   索引的屬性,屬性中存放有序對象(都是同類型的)的集合,每一個對象均可以經過一個非負的整數值(或下標)來獲取。另外,全部值的集合可使用一個數組來設 置或者獲取。做爲一個JavaBeans規範的擴展,BeanUtils包認爲全部底層數據類型爲java.util.List(或List的一個實現) 的屬性均可以被索引。
  • Mapped(映射)  --  做爲一個標準JavaBeans APIs的擴展,  BeanUtils包認爲全部底層數據類型爲java.util.Map的屬性均可以被"映射"。你能夠經過String類型的key值來設置或者獲取對應的值。

      PropertyUtils類中提供了各類API方法用來獲取和設置上述三種類型的屬性。在下面的程序片斷中,假設在bean類中都定義了以下的方法:spring

  1. public class Employee {  
  2.     public Address getAddress(String type);  
  3.     public void setAddress(String type, Address address);  
  4.     public Employee getSubordinate(int index);  
  5.     public void setSubordinate(int index, Employee subordinate);  
  6.     public String getFirstName();  
  7.     public void setFirstName(String firstName);  
  8.     public String getLastName();  
  9.     public void setLastName(String lastName); 

訪問基本屬性
      獲取和設置simple屬性很簡單。在Javadocs中查看下面兩個方法:
數據庫

  • PropertyUtils.getSimpleProperty(Object bean, String name)
  • PropertyUtils.setSimpleProperty(Object bean, String name, Object value)

     使用這兩個方法,你能夠動態地修改employee的name屬性:編程

        

  1. Employee employee = ...;  
  2. String firstName = (String) PropertyUtils.getSimpleProperty(employee, "firstName");  
  3. String lastName = (String) PropertyUtils.getSimpleProperty(employee, "lastName");  
  4.   
  5.  ... manipulate the values ...  
  6. PropertyUtils.setSimpleProperty(employee, "firstName", firstName);  
  7. PropertyUtils.setSimpleProperty(employee, "lastName", lastName); 

    對於indexed(索引)屬性,你有兩種選擇 - 你既能夠在屬性名後面添加方括號在裏面放上一個下標,也能夠在調用方法時將其做爲一個獨立參數:
數組

  • PropertyUtils.getIndexedProperty(Object bean, String name)
  • PropertyUtils.getIndexedProperty(Object bean, String name, int index)
  • PropertyUtils.setIndexedProperty(Object bean, String name, Object value)
  • PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value)
      屬性名的下標只能是整數常量。若是你想獲取的項的索引是計算出來的,你能夠將屬性名和索引做爲字符串組合起來。例如,你能夠向下面這樣作:
    Employee employee = ...;
    int index = ...;
    String name = "subordinate[" + index + "]";
    Employee subordinate = (Employee) PropertyUtils.getIndexedProperty(employee, name);

    Employee employee = ...;
    int index = ...;
    Employee subordinate = (Employee) PropertyUtils.getIndexedProperty(employee, "subordinate", index);

相似的,獲取和設置mapped(映射)屬性的方法一樣有兩對。與indexed(索引)不一樣的是額外的屬性是用括號括起來的(「(」和「)」)而不是方括號,而且獲取和設置值時如同從底層的map中獲取和設置值同樣。
  • PropertyUtils.getMappedProperty(Object bean, String name)
  • PropertyUtils.getMappedProperty(Object bean, String name, String key)
  • PropertyUtils.setMappedProperty(Object bean, String name, Object value)
  • PropertyUtils.setMappedProperty(Object bean, String name, String key, Object value)

例如,你可使用下面兩種方法設置employee的家庭住址:緩存

    Employee employee = ...;
    Address address = ...;
    PropertyUtils.setMappedProperty(employee, "address(home)", address);

    Employee employee = ...;
    Address address = ...;
    PropertyUtils.setMappedProperty(employee, "address", "home", address);
服務器


訪問嵌套屬性
      在上面的例子中,咱們假設你將bean做爲第一個參數傳入PropertyUtils方法,並但願獲取指定屬性的值。然而,若是屬性的值是一個Java對象,而且你但願進一步獲取這個Java對象的某個屬性的值?

      例如,假設你事實上想要獲取的值是employee家庭住址中的city屬性。使用標準的Java編程技術直接獲取bean的對應屬性,你能夠這樣寫:

   String city = employee.getAddress("home").getCity();


      使用PropertyUtils類中的等效機制被稱爲嵌套屬性訪問。使用這種方法,你將訪問路徑上的屬性的名稱用「.」拼接起來 --與你在JavaScript執行嵌套屬性訪問的方式很是類似。

  • PropertyUtils.getNestedProperty(Object bean, String name)
  • PropertyUtils.setNestedProperty(Object bean, String name, Object value)
      PropertyUtils中等效於上面的Java代碼將是這樣:

   String city = (String) PropertyUtils.getNestedProperty(employee, "address(home).city");


最後,方便起見,PropertyUtils提供了以下一組方法,它們接收simple、indexed和mapped屬性的任意組合方法,支持任意層次的嵌套:

  • PropertyUtils.getProperty(Object bean, String name)
  • PropertyUtils.setProperty(Object bean, String name, Object value)
      你能夠像這樣使用:

    Employee employee = ...;
    String city = (String) PropertyUtils.getProperty(employee,"subordinate[3].address(home).city");

2、java反射和內省
概述;
1.什麼是反射
反射就是在運行狀態把 Java 類中的各類成分映射成相應相應的 Java 類,能夠動態得獲取全部的屬性以及動態調用任意一個方法。
1).一段java代碼在程序的運行期間會經歷三個階段:source-->class-->runtime
2).Class對象
在java中用一個Class對象來表示一個java類的class階段
Class對象封裝了一個java類定義的成員變量、成員方法、構造方法、包名、類名等。
2.反射怎麼用
1).得到java類的各個組成部分,首先須要得到表明java類的Class對象
得到Class對象有如下三種方式:
Class.forname(className) 用於作類加載
obj.getClass() 用於得到對象的類型
類名.class 用於得到指定的類型,傳參用
2).反射類的構造方法,得到實例
Class clazz = 類名.class;
Constuctor con = clazz.getConstructor(new Class[]{paramClazz1,paramClazz2,.....});
con.newInstance(params....);
3).反射類的成員方法
Method m = clazz.getMethod(methodName,new Class[]{paramClazz1,paramClazz2,.....});
m.invoke();
4).反射類的屬性
Field field = clazz.getField(fieldName);
field.setAccessible(true);//設置爲可訪問
filed.setObject(value); //設置值
Object value = field.get(clazz); //得到值
Object staticValue = filed.get(Class); //得到靜態值

二:內省
1.什麼是內省
經過反射的方式操做JavaBean的屬性,jdk提供了PropertyDescription類來操做訪問JavaBean的屬性,Beantils工具基於此來實現。
2.內省怎麼用
1).操做一個屬性
Object obj = new Object();
PropertyDescriptor pd = new PropertyDescriptor(propertyName,Class); //聲明屬性描述對象,一次只可描述一個屬性
Method m = pd.getWriterMethod();//獲取setter方法
m.invoke(obj,value);
Method m = pd.getReaderMethod();//獲取getter方法
Object value = m.invoke(obj);
2).操做多個屬性
BeanInfo bi = Instospector.getBeanInfo(beanClass);//獲取Bean描述對象
PropertyDescriptor[] pds = bi.getPropertyDescriptors();//獲取屬性描述對象數組
拿到屬性描述對象數組以後再循環數組,剩餘的操做就跟"操做一個屬性"相同了。

反射

相對而言,反射比內省更容易理解一點。用一句比較白的話來歸納,反射就是讓你能夠經過名稱來獲得對象(類,屬性,方法)的技術。例如咱們能夠經過類 名來生成一個類的實例;知道了方法名,就能夠調用這個方法;知道了屬性名就能夠訪問這個屬性的值,仍是寫兩個例子讓你們更直觀的瞭解反射的使用方法:

  1. //經過類名來構造一個類的實例  
  2. ClassClasscls_str=Class.forName("java.lang.String");  
  3. //上面這句很眼熟,由於使用過JDBC訪問數據庫的人都用過J  
  4. Objectstr=cls_str.newInstance();  
  5. //至關於Stringstr=newString();  
  6.  
  7. //經過方法名來調用一個方法  
  8. StringmethodName="length";  
  9. Methodm=cls_str.getMethod(methodName,null);  
  10. System.out.println("lengthis"+m.invoke(str,null));  
  11. //至關於System.out.println(str.length());  

上面的兩個例子是比較經常使用方法。看到上面的例子就有人要發問了:爲何要這麼麻煩呢?原本一條語句就完成的事情幹嘛要整這麼複雜?沒錯,在上面的例 子中確實沒有必要這麼麻煩。不過你想像這樣一個應用程序,它支持動態的功能擴展,也就是說程序不從新啓動可是能夠自動加載新的功能,這個功能使用一個具體 類來表示。首先咱們必須爲這些功能定義一個接口類,而後咱們要求全部擴展的功能類必須實現我指定的接口,這個規定了應用程序和可擴展功能之間的接口規則, 可是怎麼動態加載呢?咱們必須讓應用程序知道要擴展的功能類的類名,好比是test.Func1,當咱們把這個類名(字符串)告訴應用程序後,它就可使 用咱們第一個例子的方法來加載並啓用新的功能。這就是類的反射,請問你有別的選擇嗎?

內省

內省是Java語言對Bean類屬性、事件的一種缺省處理方法。例如類A中有屬性name,那咱們能夠經過getName,setName來獲得其 值或者設置新的值。經過getName/setName來訪問name屬性,這就是默認的規則。Java中提供了一套API用來訪問某個屬性的 getter/setter方法,經過這些API可使你不須要了解這個規則,這些API存放於包java.beans中。

通常的作法是經過類Introspector來獲取某個對象的BeanInfo信息,而後經過BeanInfo來獲取屬性的描述器 (PropertyDescriptor),經過這個屬性描述器就能夠獲取某個屬性對應的getter/setter方法,而後咱們就能夠經過反射機制來 調用這些方法。下面咱們來看一個例子,這個例子把某個對象的全部屬性名稱和值都打印出來:

  1. /*  
  2. *Createdon2004-6-29  
  3. */  
  4.  
  5. packagedemo;  
  6.  
  7. importjava.beans.BeanInfo;  
  8. importjava.beans.Introspector;  
  9. importjava.beans.PropertyDescriptor;  
  10.  
  11. publicclassIntrospectorDemo{  
  12. Stringname;  
  13. publicstaticvoidmain(String[]args)throwsException{  
  14. IntrospectorDemodemo=newIntrospectorDemo();  
  15. demo.setName("WinterLau");  
  16.  
  17. //若是不想把父類的屬性也列出來的話,  
  18. //那getBeanInfo的第二個參數填寫父類的信息  
  19. BeanInfobi=Introspector.getBeanInfo(demo.getClass(),Object.class);  
  20. PropertyDescriptor[]props=bi.getPropertyDescriptors();  
  21. for(inti=0;i<props.length;i++){  
  22. System.out.println(props[i].getName()+"="+  
  23. props[i].getReadMethod().invoke(demo,null));  
  24. }  
  25.  
  26. }  
  27.  
  28. publicStringgetName(){  
  29. returnname;  
  30. }  
  31.  
  32. publicvoidsetName(Stringname){  
  33. this.name=name;  
  34. }  
  35. }  

Web開發框架Struts中的FormBean就是經過內省機制來將表單中的數據映射到類的屬性上,所以要求FormBean的每一個屬性要有 getter/setter方法。但也並不老是這樣,什麼意思呢?就是說對一個Bean類來說,我能夠沒有屬性,可是隻要有getter/setter方 法中的其中一個,那麼Java的內省機制就會認爲存在一個屬性,好比類中有方法setMobile,那麼就認爲存在一個mobile的屬性,這樣能夠方便 咱們把Bean類經過一個接口來定義而不用去關心具體實現,不用去關心Bean中數據的存儲。好比咱們能夠把全部的getter/setter方法放到接 口裏定義,可是真正數據的存取則是在具體類中去實現,這樣可提升系統的擴展性。

總結

將Java的反射以及內省應用到程序設計中去能夠大大的提供程序的智能化和可擴展性。有不少項目都是採起這兩種技術來實現其核心功能,例如咱們前面 提到的Struts,還有用於處理XML文件的Digester項目,其實應該說幾乎全部的項目都或多或少的採用這兩種技術。在實際應用過程當中兩者要相互 結合方能發揮真正的智能化以及高度可擴展性。


3、缺點及優化

在web.xml中註冊IntrospectorCleanupListener監聽器以解決struts等框架可能產生的內存泄露問題

增長方式以下:

  1.     <listener>  
  2.         <listener-class>  
  3.             org.springframework.web.util.IntrospectorCleanupListener  
  4.         </listener-class>  
  5.     </listener>  
[html] view plain copy 在CODE上查看代碼片派生到個人代碼片
  1.     <listener>  
  2.         <listener-class>  
  3.             org.springframework.web.util.IntrospectorCleanupListener  
  4.         </listener-class>  
  5.     </listener>  

org.springframework.web.util.IntrospectorCleanupListener源代碼中對其的解釋以下:

        Listener that flushes the JDK's JavaBeans Introspector cache on web app shutdown. Register this listener in your web.xml to guarantee proper release of the web application class loader and its loaded classes.

        在Web應用程序關閉時IntrospectorCleanupListener將會刷新JDK的JavaBeans的Introspector緩存。在 你的web.xml中註冊這個listener來確保Web應用程序的類加載器以及其加載的類正確的釋放資源。

        If the JavaBeans Introspector has been used to analyze application classes, the system-level Introspector cache will hold a hard reference to those classes. Consequently, those classes and the web application class loader will not be garbage-collected on web app shutdown! This listener performs proper cleanup, to allow for garbage collection to take effect.      

       若是JavaBeans的Introspector已被用來分析應用程序類,系統級的Introspector緩存將持有這些類的一 個硬引用。所以,這些類和Web應用程序的類加載器在Web應用程序關閉時將不會被垃圾收集器回收!而 IntrospectorCleanupListener則會對其進行適當的清理,已使其可以被垃圾收集器回收。

       Unfortunately, the only way to clean up the Introspector is to flush the entire cache, as there is no way to specifically determine the application's classes referenced there. This will remove cached introspection results for all other applications in the server too.

       不幸的是,惟一可以清理Introspector的方法是刷新整個Introspector緩存,沒有其餘辦法來確切指定應用程序所引用的類。這將刪除全部其餘應用程序在服務器的緩存的Introspector結果。

       spring's beans infrastructure within the application, as Spring's own introspection results cache will immediately flush an analyzed class from the JavaBeans Introspector cache and only hold a cache within the application's own ClassLoader. Although Spring itself does not create JDK Introspector leaks, note that this listener should nevertheless be used in scenarios where the Spring framework classes themselves reside in a 'common' ClassLoader (such as the system ClassLoader). In such a scenario, this listener will properly clean up Spring's introspection cache.

       請注意,在使用Spring內部的bean機制時,不須要使用此監聽器,由於Spring本身的introspection results cache將會當即刷新被分析過的JavaBeans Introspector cache,而僅僅會在應用程序本身的ClassLoader裏面持有一個cache。雖然Spring自己不產生泄漏,注意,即便在Spring框架的 類自己駐留在一個「共同」類加載器(如系統的ClassLoader)的狀況下,也仍然應該使用使用 IntrospectorCleanupListener。在這種狀況下,這個IntrospectorCleanupListener將會妥善清理 Spring的introspection cache。

       Application classes hardly ever need to use the JavaBeans Introspector directly, so are normally not the cause of Introspector resource leaks. Rather, many libraries and frameworks do not clean up the Introspector: e.g. Struts and Quartz.

       應用程序類,幾乎不須要直接使用JavaBeans Introspector,因此,一般都不是Introspector resource形成內存泄露。相反,許多庫和框架,不清理Introspector,例如: Struts和Quartz。

       Note that a single such Introspector leak will cause the entire web app class loader to not get garbage collected! This has the consequence that you will see all the application's static class resources (like singletons) around after web app shutdown, which is not the fault of those classes!

        須要注意的是一個簡單Introspector泄漏將會致使整個Web應用程序的類加載器不會被回收!這樣作的結果,將會是在web應用程序關閉時,該應 用程序全部的靜態類資源(好比:單實例對象)都沒有獲得釋放。而致使內存泄露的根本緣由其實並非這些未被回收的類!

This listener should be registered as the first one in web.xml, before any application listeners such as Spring's ContextLoaderListener. This allows the listener to take full effect at the right time of the lifecycle. 

       IntrospectorCleanupListener應該註冊爲web.xml中的第一個Listener,在任何其餘 Listener以前註冊,好比在Spring's ContextLoaderListener註冊以前,才能確保IntrospectorCleanupListener在Web應用的生命週期適當時機 生效。

相關文章
相關標籤/搜索