java內省機制及PropertyUtils使用方法


背景

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

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

     JavaBean支持的屬性類型一般可以划分成三類--標准的JavaBeans規范支持其中的一些,也有一部分只有BeanUtils包支持:

  • 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類中都定義了如下的方法:

  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");

二、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項目,其實應該說幾乎所有的項目都或多或少的采用這兩種技術。在實際應用過程中二者要相互 結合方能發揮真正的智能化以及高度可擴展性。


三、缺點及優化

在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應用的生命周期適當時機 生效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM