在分析IntrospectorCleanupListener以前,先了解一下Introspector。Introspector是JDK中java.beans包下的類,它爲目標JavaBean提供了一種瞭解原類方法、屬性和事件的標準方法。通俗的說,就是能夠經過Introspector構建一個BeanInfo對象,而這個BeanInfo對象中包含了目標類中的屬性、方法和事件的描述信息,而後可使用這個BeanInfo對象對目標對象進行相關操做。java
下面看一個簡單的示例會很容易明白。爲了簡單,Student類中只有一個name屬性。web
結果輸出:Student{name='張三'}
複製代碼
經過查看Introspector.getBeanInfo方法的源碼會發現,Introspector在構建一個BeanInfo對象的時候,會將構建的BeanInfo對象和原類緩存到一個Map中,源碼以下。spring
經過上的代碼能夠得出,Introspector間接持有了BeanInfo的強引用。若是使用Introspector操做了不少類,那麼Introspector將間接持有這些BeanInfo的強引用。在發生垃圾收集的時候,檢測到這些BeanInfo存在引用鏈,則這些類和對應的類加載器將不會被垃圾收集器回收,進而致使內存泄漏。因此,爲了解決這個問題,在使用Introspector操做完成後,調用Introspector類的flushCaches方法清除緩存。緩存
經過上面的代碼會發現,清除的時候是清空了整個緩存,由於沒有很好的辦法來肯定每一個緩存是屬於哪一個應用的,因此清除的時候會清除全部應用的緩存。bash
上面分析了Introspector的做用和影響,那IntrospectorCleanupListener和Introspector有什麼關係呢?
IntrospectorCleanupListener是spring-web jar中的類,源碼以下。框架
IntrospectorCleanupListener實現了ServletContextListener接口,也就是說,在web容器初始化(準確的說是在filters或servlets初始化以前)的時候會執行contextInitialized方法,在ServletContext銷燬(準確的說是在filters和servlets銷燬以後)的時候會執行contextDestroyed方法。從圖中contextDestroyed方法,能夠看到在銷燬ServletContext的時候調用了Introspector.flushCaches方法,清空了對應緩存。IntrospectorCleanupListener中爲何要這麼作?難道是Spring使用Introspector操做後沒有清空對應緩存?查看IntrospectorCleanupListener類的源碼,會發現有這樣一段標註。spa
大意是說,在使用Spring自己的時候並不須要使用此監聽器,由於Spring本身的內部機制會當即清空對應的緩存。雖然,Spring自己不存在這樣的問題,可是若是和其它框架結合使用,而其它框架有這個問題,如Struts、Quartz等,那就須要配置這個監聽器,在銷燬ServletContext的時候清空對應緩存。code
有一點須要注意的是,像這樣一個簡單的Introspector內存泄漏將會致使整個應用的類加載器不會被垃圾收集器回收,若是有內存泄漏的問題,能夠考慮此因素。cdn
在以往的工做經歷中,屢次看到在web.xml中將IntrospectorCleanupListener配置成非第一個listener。xml
其實,看過源碼的都知道,官方的表述是必須將此監聽器配置成web.xml中的第一個listener,才能在合適的時間發揮最有效的做用。
緣由其實很簡單,在Servlet3.0規範以前,監聽器的調用是隨機的,而從Servlet3.0開始,監聽器的調用順序是根據其在web.xml中配置的順序,而且實現ServletContextListener的監聽器,contextInitialized方法調用順序是按照在web.xml中配置的順序正序依次執行,而contextDestroyed方法的調用順序是按照在web.xml中配置的順序逆序依次執行。因此,若是IntrospectorCleanupListener被配置成了第一個listener,那麼它的contextDestroyed方法將最後一個執行,將發揮最有效的清除做用;而若是不是,那麼可能會殘留未被清除的緩存。