背景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
PropertyUtils類中提供了各類API方法用來獲取和設置上述三種類型的屬性。在下面的程序片斷中,假設在bean類中都定義了以下的方法:spring
訪問基本屬性
獲取和設置simple屬性很簡單。在Javadocs中查看下面兩個方法:
數據庫
使用這兩個方法,你能夠動態地修改employee的name屬性:編程
對於indexed(索引)屬性,你有兩種選擇 - 你既能夠在屬性名後面添加方括號在裏面放上一個下標,也能夠在調用方法時將其做爲一個獨立參數:
數組
例如,你可使用下面兩種方法設置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對象的某個屬性的值?
String city = employee.getAddress("home").getCity();
使用PropertyUtils類中的等效機制被稱爲嵌套屬性訪問。使用這種方法,你將訪問路徑上的屬性的名稱用「.」拼接起來 --與你在JavaScript執行嵌套屬性訪問的方式很是類似。
String city = (String) PropertyUtils.getNestedProperty(employee, "address(home).city");
最後,方便起見,PropertyUtils提供了以下一組方法,它們接收simple、indexed和mapped屬性的任意組合方法,支持任意層次的嵌套:
反射
相對而言,反射比內省更容易理解一點。用一句比較白的話來歸納,反射就是讓你能夠經過名稱來獲得對象(類,屬性,方法)的技術。例如咱們能夠經過類 名來生成一個類的實例;知道了方法名,就能夠調用這個方法;知道了屬性名就能夠訪問這個屬性的值,仍是寫兩個例子讓你們更直觀的瞭解反射的使用方法:
上面的兩個例子是比較經常使用方法。看到上面的例子就有人要發問了:爲何要這麼麻煩呢?原本一條語句就完成的事情幹嘛要整這麼複雜?沒錯,在上面的例 子中確實沒有必要這麼麻煩。不過你想像這樣一個應用程序,它支持動態的功能擴展,也就是說程序不從新啓動可是能夠自動加載新的功能,這個功能使用一個具體 類來表示。首先咱們必須爲這些功能定義一個接口類,而後咱們要求全部擴展的功能類必須實現我指定的接口,這個規定了應用程序和可擴展功能之間的接口規則, 可是怎麼動態加載呢?咱們必須讓應用程序知道要擴展的功能類的類名,好比是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方法,而後咱們就能夠經過反射機制來 調用這些方法。下面咱們來看一個例子,這個例子把某個對象的全部屬性名稱和值都打印出來:
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等框架可能產生的內存泄露問題
增長方式以下:
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應用的生命週期適當時機 生效。