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 頁面關(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ù)庫連接池連接失敗后一直重連問題
這篇文章主要介紹了解決springboot?druid數(shù)據(jù)庫連接池連接失敗后一直重連問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
SpringBoot集成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-09
SpringCloud Gateway的路由,過濾器和限流解讀
這篇文章主要介紹了SpringCloud Gateway的路由,過濾器和限流解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
spring中的特殊注解@RequiredArgsConstructor詳解
這篇文章主要介紹了spring中的特殊注解@RequiredArgsConstructor,包括注解注入,構(gòu)造器注入及setter注入,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
Mybatis generator自動(dòng)生成代碼插件實(shí)例解析
這篇文章主要介紹了Mybatis generator自動(dòng)生成代碼插件實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09

