Java內(nèi)省之Introspector解讀
Java內(nèi)省之Introspector
在JavaBean規(guī)范中有如下描述:
大意是java默認(rèn)情況下jdk使用低級(jí)的反射機(jī)制來分析Bean,為了方便其他人分析bean,java提供了一個(gè)內(nèi)省類Introspector,使用Introspector的getBeanInfo方法可以獲取一個(gè)封裝了bean信息(包括屬性和方法)的BeanInfo對(duì)象。
Introspector使用不當(dāng)導(dǎo)致內(nèi)存泄露的風(fēng)險(xiǎn)
框架幾乎都使用了Introspector類來實(shí)現(xiàn)靈活性,但是Introspector在獲取beanInfo對(duì)象時(shí),為了提高性能使用了緩存保存beanInfo:
public static BeanInfo getBeanInfo(Class<?> beanClass) ? ? ? ? throws IntrospectionException ? ? { ? ? ? ? if (!ReflectUtil.isPackageAccessible(beanClass)) { ? ? ? ? ? ? return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo(); ? ? ? ? } ? ? ? ? ThreadGroupContext context = ThreadGroupContext.getContext(); ? ? ? ? BeanInfo beanInfo; ? ? ? ? synchronized (declaredMethodCache) { ? ? ? ? ? ? beanInfo = context.getBeanInfo(beanClass); ? ? ? ? } ? ? ? ? if (beanInfo == null) { ? ? ? ? ? ? beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo(); ? ? ? ? ? ? synchronized (declaredMethodCache) { ? ? ? ? ? ? ? ? context.putBeanInfo(beanClass, beanInfo); ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return beanInfo; ? ? }
緩存使用ThreadGroupContext——線程組級(jí)別共享,類似與ThreadLocal。
內(nèi)部使用WeakHashMap——key為弱引用來保存beanInfo,其中使用class作為key,beanInfo作為value。
同時(shí)使用WeakIdentityMap保存ThreadGroupContext對(duì)象(應(yīng)該是ThreadGroupContext對(duì)象的hash值)與WeakHashMap的映射關(guān)系,也就是說不同線程組相互隔離:
final class ThreadGroupContext { //WeakIdentityMap 判斷key是否重復(fù)只判斷hash是否相等,不調(diào)用equals ? ? private static final WeakIdentityMap<ThreadGroupContext> contexts = new WeakIdentityMap<ThreadGroupContext>() { ? ? ? ? protected ThreadGroupContext create(Object key) { ? ? ? ? ? ? return new ThreadGroupContext(); ? ? ? ? } ? ? }; ? ? /** ? ? ?* Returns the appropriate {@code ThreadGroupContext} for the caller, ? ? ?* as determined by its {@code ThreadGroup}. ? ? ?* ? ? ?* @return ?the application-dependent context ? ? ?*/ ? ? static ThreadGroupContext getContext() { ? ? ? ? return contexts.get(Thread.currentThread().getThreadGroup()); ? ? } 。 。 。 }
但是beanInfo中持有class對(duì)象,因此WeakHashMap的弱引用失效,Introspector提供了清除緩存的方法flushCaches。
但有些框架在使用Introspector之后并沒有清除緩存
在spring中有如下描述:
/** ?* Listener that flushes the JDK's {@link java.beans.Introspector JavaBeans Introspector} ?* cache on web app shutdown. Register this listener in your {@code web.xml} to ?* guarantee proper release of the web application class loader and its loaded classes. ?* ?* <p><b>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!</b> This listener performs proper cleanup, ?* to allow for garbage collection to take effect. ?* ?* <p>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. ?* ?* <p>Note that this listener is <i>not</i> necessary when using 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. ?* ?* <b>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).</b> ?* In such a scenario, this listener will properly clean up Spring's introspection cache. ?* ?* <p>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. ?* ?* <p>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! ?* ?* <p><b>This listener should be registered as the first one in {@code web.xml}, ?* before any application listeners such as Spring's ContextLoaderListener.</b> ?* This allows the listener to take full effect at the right time of the lifecycle.
大意是在web應(yīng)用中使用Introspector分析bean,當(dāng)web應(yīng)用停止時(shí)(這里應(yīng)該指的是正常銷毀,而非殺死進(jìn)程暴力銷毀),由于Introspector持有被分析bean的強(qiáng)引用,導(dǎo)致bean以及加載bean的classload無法被gc,造成內(nèi)存泄露。
個(gè)人猜想,如果web應(yīng)用停止之后,main方法運(yùn)行結(jié)束,jvm退出應(yīng)該不存在內(nèi)存泄露的情況。但是,當(dāng)web服務(wù)銷毀之后main方法還在執(zhí)行,那么就出現(xiàn)內(nèi)存泄露。例如在一個(gè)tomcat中部署多個(gè)應(yīng)用,在tomcat的manager App 頁(yè)面關(guān)閉應(yīng)用就會(huì)導(dǎo)致內(nèi)存泄露。
大部分框架在創(chuàng)建線程池的時(shí)候都繼承parentThreadGroup,因此即使使用WeakIdentityMap保存ThreadGroup對(duì)象的軟引用與WeakHashMap的映射關(guān)系,但其他未關(guān)閉的web應(yīng)用仍然持有ThreadGroup的強(qiáng)引用,因此WeakIdentityMap中的beanInfo緩存不會(huì)被回收——內(nèi)存泄露。
IntrospectorCleanupListener
為了解決其他框架如:
Struts和Quartz(大部分博客均指出這兩個(gè)框架使用Introspector后沒有flushCaches,但我沒有考證),一心為我們考慮的spring提供了解決方案 ——IntrospectorCleanupListener:
public class IntrospectorCleanupListener implements ServletContextListener { ? ? @Override ? ? public void contextInitialized(ServletContextEvent event) { ? ? ? ? CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader()); ? ? } ? ? @Override ? ? public void contextDestroyed(ServletContextEvent event) { ? ? ? ? CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader()); ? ? ? ? Introspector.flushCaches(); ? ? } }
IntrospectorCleanupListener是servletContext的監(jiān)聽器,在servletContext銷毀時(shí),會(huì)執(zhí)行contextDestroyed方法,調(diào)用Introspector.flushCaches(),防止內(nèi)存泄露。
spring同時(shí)說明spring框架沒有使用Introspector的緩存,而是使用Introspector分析bean之后,隨即清理了Introspector緩存,并使用自己的緩存邏輯進(jìn)行緩存,應(yīng)該就是
CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader())
這行代碼實(shí)現(xiàn)——未考證,因此spring聲明在只使用spring框架時(shí)不需要考慮introspector導(dǎo)致內(nèi)存泄露的問題。
但個(gè)人認(rèn)為,如果某個(gè)框架在創(chuàng)建自己的線程池時(shí),傳入了新的ThreadGroup對(duì)象,那么IntrospectorCleanupListener 可能也無法工作。
Java內(nèi)省Introspector應(yīng)用
IntroSpecor介紹
內(nèi)省(IntroSpector)是Java語言對(duì)JavaBean 類屬性、事件的一種缺省處理方法。
例如類A中有屬性name, 那我們可以通過getName,setName 來得到其值或者設(shè)置新的值。
通過getName/setName 來訪問name屬性,這就是默認(rèn)的規(guī)則。
Java中提供了一套API 用來訪問某個(gè)屬性的getter/setter方法,通過這些API 可以使你不需要了解這個(gè)規(guī)則,這些API存放于包java.beans 中。
Class Diagram
一般的做法是通過類Introspector的getBeanInfo方法獲取某個(gè)對(duì)象的BeanInfo 信息,然后通過BeanInfo來獲取屬性的描述器(PropertyDescriptor),通過這個(gè)屬性描述器就可以獲取某個(gè)屬性對(duì)應(yīng)的getter/setter方法,然后我們就可以通過反射機(jī)制來調(diào)用這些方法。
我們又通常把javabean的實(shí)例對(duì)象稱之為值對(duì)象(Value Object),因?yàn)檫@些bean中通常只有一些信息字段和存儲(chǔ)方法,沒有功能性方法。
一個(gè)JavaBean類可以不當(dāng)JavaBean用,而當(dāng)成普通類用。JavaBean實(shí)際就是一種規(guī)范,當(dāng)一個(gè)類滿足這個(gè)規(guī)范,這個(gè)類就能被其它特定的類調(diào)用。一個(gè)類被當(dāng)作javaBean使用時(shí),JavaBean的屬性是根據(jù)方法名推斷出來的,它根本看不到j(luò)ava類內(nèi)部的成員變量。去掉set前綴,然后取剩余部分,如果剩余部分的第二個(gè)字母是小寫的,則把剩余部分的首字母改成小的。
除了反射用到的類需要引入外,內(nèi)省需要引入的類如下所示,它們都屬于java.beans包中的類,自己寫程序的時(shí)候也不能忘了引入相應(yīng)的包或者類。
簡(jiǎn)單示例1
下面代碼片斷是設(shè)置某個(gè)JavaBean類某個(gè)屬性的關(guān)鍵代碼:
package com.jasun.test; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.apache.commons.beanutils.BeanUtils; publicclass IntrospectorTest { publi static void main(String[] args) throws IllegalArgumentException, IntrospectionException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { UserInfo userInfo=new UserInfo("zhangsan", "123456"); String propertyName="userName"; Object retVal=getProperty(userInfo, propertyName); System.out.println("retVal="+retVal); //retVal=zhangsan Object value="abc"; setProperty(userInfo, propertyName, value); retVal=getProperty(userInfo, propertyName); System.out.println("retVal="+retVal); //retVal=abc //使用BeanUtils工具包操作JavaBean String userName=BeanUtils.getProperty(userInfo, propertyName); System.out.println("userName="+userName); BeanUtils.setProperty(userInfo, propertyName, "linjiqin"); userName=BeanUtils.getProperty(userInfo, propertyName); System.out.println("userName="+userName); } /** * 設(shè)置屬性 * * @param clazz 對(duì)象名 * @param propertyName 屬性名 * @param value 屬性值 */ private static void setProperty(Object clazz, String propertyName, Object value) throws IntrospectionException,IllegalAccessException, InvocationTargetException{ //方法一 /*PropertyDescriptor pd=new PropertyDescriptor(propertyName, clazz.getClass()); Method methodSet=pd.getWriteMethod(); methodSet.invoke(clazz, value);*/ //方法二 BeanInfo beanInfo=Introspector.getBeanInfo(clazz.getClass()); PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors(); for(PropertyDescriptor pd:pds){ if(propertyName.equals(pd.getName())){ Method methodSet=pd.getWriteMethod(); methodSet.invoke(clazz, value); break; } } } /** * 獲取屬性 * * @param clazz 對(duì)象名 * @param propertyName 屬性名 * @return * @throws IntrospectionException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ private static Object getProperty(Object clazz, String propertyName) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{ //方法一 /*PropertyDescriptor pd=new PropertyDescriptor(propertyName, clazz.getClass()); Method methodGet=pd.getReadMethod(); return methodGet.invoke(clazz);*/ //方法二 Object retVal=null; BeanInfo beanInfo=Introspector.getBeanInfo(clazz.getClass()); PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors(); for(PropertyDescriptor pd:pds){ if(propertyName.equals(pd.getName())){ Method methodGet=pd.getReadMethod(); retVal=methodGet.invoke(clazz); break; } } return retVal; } }
UserInfo類
package com.ljq.test; publicclass UserInfo { private String userName; private String pwd; public UserInfo(String userName, String pwd) { super(); this.userName = userName; this.pwd = pwd; } public String getUserName() { return userName; } publicvoid setUserName(String userName) { this.userName = userName; } public String getPwd() { return pwd; } publicvoid setPwd(String pwd) { this.pwd = pwd; } }
簡(jiǎn)單示例2::(僅作參考)
package com.siyuan.jdktest; import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; class Person { private String name; private int age; /** * @return the age */ public int getAge() { return age; } /** * @param age the age to set */ public void setAge(int age) { this.age = age; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } } public class IntrospectorTest { /** * @param args * @throws IntrospectionException */ public static void main(String[] args) throws IntrospectionException { // TODO Auto-generated method stub BeanInfo beanInfo = Introspector.getBeanInfo(Person.class); System.out.println("BeanDescriptor==========================================="); BeanDescriptor beanDesc = beanInfo.getBeanDescriptor(); Class cls = beanDesc.getBeanClass(); System.out.println(cls.getName()); System.out.println("MethodDescriptor==========================================="); MethodDescriptor[] methodDescs = beanInfo.getMethodDescriptors(); for (int i = 0; i < methodDescs.length; i++) { Method method = methodDescs[i].getMethod(); System.out.println(method.getName()); } System.out.println("PropertyDescriptor==========================================="); PropertyDescriptor[] propDescs = beanInfo.getPropertyDescriptors(); for (int i = 0; i < propDescs.length; i++) { Method methodR = propDescs[i].getReadMethod(); if (methodR != null) { System.out.println(methodR.getName()); } Method methodW = propDescs[i].getWriteMethod(); if (methodW != null) { System.out.println(methodW.getName()); } } } }
運(yùn)行結(jié)果
BeanDescriptor===========================================
com.siyuan.jdktest.Person
MethodDescriptor===========================================
hashCode
setAge
equals
wait
wait
notify
getClass
toString
getAge
notifyAll
setName
wait
getName
PropertyDescriptor===========================================
getAge
setAge
getClass
getName
setName
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring的CorsFilter會(huì)失效的原因及解決方法
眾所周知CorsFilter是Spring提供的跨域過濾器,我們可能會(huì)做以下的配置,基本上就是允許任何跨域請(qǐng)求,我利用Spring的CorsFilter做跨域操作但是出現(xiàn)報(bào)錯(cuò),接下來小編就給大家介紹一Spring的CorsFilter會(huì)失效的原因及解決方法,需要的朋友可以參考下2023-09-09解決springboot?druid數(shù)據(jù)庫(kù)連接池連接失敗后一直重連問題
這篇文章主要介紹了解決springboot?druid數(shù)據(jù)庫(kù)連接池連接失敗后一直重連問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Jedis操作Redis數(shù)據(jù)庫(kù)的方法
這篇文章主要為大家詳細(xì)介紹了Jedis操作Redis數(shù)據(jù)庫(kù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04SpringBoot集成Redisson實(shí)現(xiàn)延遲隊(duì)列的場(chǎng)景分析
這篇文章主要介紹了SpringBoot集成Redisson實(shí)現(xiàn)延遲隊(duì)列,本文通過場(chǎng)景分析實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04如何用idea編寫并運(yùn)行第一個(gè)spark scala處理程序
詳細(xì)介紹了如何使用IntelliJ IDEA創(chuàng)建Scala項(xiàng)目,包括配置JDK和Scala SDK,添加Maven支持,編輯pom.xml,并創(chuàng)建及運(yùn)行Scala程序,這為Scala初學(xué)者提供了一個(gè)基礎(chǔ)的項(xiàng)目搭建和運(yùn)行指南2024-09-09SpringCloud Gateway的路由,過濾器和限流解讀
這篇文章主要介紹了SpringCloud Gateway的路由,過濾器和限流解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02spring中的特殊注解@RequiredArgsConstructor詳解
這篇文章主要介紹了spring中的特殊注解@RequiredArgsConstructor,包括注解注入,構(gòu)造器注入及setter注入,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04Mybatis generator自動(dòng)生成代碼插件實(shí)例解析
這篇文章主要介紹了Mybatis generator自動(dòng)生成代碼插件實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09