java內(nèi)省(Introspector)
java內(nèi)省優(yōu)化工具類(lèi)BeanUtils(優(yōu)化內(nèi)省并防止內(nèi)存泄漏)
Spring中專(zhuān)門(mén)提供了用于緩存JavaBean的PropertyDescriptor描述信息的類(lèi)——CachedIntrospectionResults。但它的forClass()(獲取對(duì)象)訪問(wèn)權(quán)限時(shí)default,不能被應(yīng)用代碼直接使用。但是可以通過(guò)org.springframework.beans.BeanUtils工具類(lèi)來(lái)使用。
@Nullable
public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName)
throws BeansException {
//工廠模式,獲取到對(duì)應(yīng)的CachedIntrospectionResults 對(duì)象
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptor(propertyName);
}
CachedIntrospectionResults這個(gè)類(lèi)使用的是工廠模式,通過(guò)forClass()方法獲取到不同的CachedIntrospectionResults對(duì)象。
實(shí)際上使用的是ConcurrentHashMap進(jìn)行存儲(chǔ),key就是Class對(duì)象,而value是CachedIntrospectionResults對(duì)象。
1. CachedIntrospectionResults基本結(jié)構(gòu)

當(dāng)使用JavaBean的內(nèi)省時(shí),使用Introspector,jdk會(huì)自動(dòng)緩存內(nèi)省信息(BeanInfo),這一點(diǎn)是可以理解的,畢竟內(nèi)省通過(guò)反射的代價(jià)是高昂的。當(dāng)ClassLoader關(guān)閉時(shí),Introspector的緩存持有BeanInfo的信息,而B(niǎo)eanInfo持有Class的強(qiáng)引用,這會(huì)導(dǎo)致ClassLoader和它引用的Class等對(duì)象不能被回收。
獲取CachedIntrospectionResults的工廠方法:
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
//緩存中取
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
//開(kāi)始創(chuàng)建出CachedIntrospectionResults對(duì)象
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
//確保Spring框架的jar包和應(yīng)用類(lèi)的jar包使用的是同一個(gè)ClassLoader加載的,這樣的話,會(huì)允許隨著Spring容器的生命周期來(lái)清除緩存。
//當(dāng)然若是多類(lèi)加載器的應(yīng)用,判斷應(yīng)用類(lèi)使用的類(lèi)加載器是否是安全的。使得類(lèi)加載器過(guò)期時(shí),能即時(shí)清除緩存中的值。
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
classCacheToUse = softClassCache;
}
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
請(qǐng)注意CachedIntrospectionResults使用的是兩個(gè)緩存。
/**
* Map keyed by Class containing CachedIntrospectionResults, strongly held.
* This variant is being used for cache-safe bean classes.
*/
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache =
new ConcurrentHashMap<>(64);
/**
* Map keyed by Class containing CachedIntrospectionResults, softly held.
* This variant is being used for non-cache-safe bean classes.
*/
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache =
new ConcurrentReferenceHashMap<>(64);
- 若應(yīng)用類(lèi)和Spring容器類(lèi)使用的是一個(gè)類(lèi)加載器 || 應(yīng)用類(lèi)使用的加載器是用戶(hù)提前緩存的類(lèi)加載器,那么存入
strongClassCache對(duì)象中。 - 若應(yīng)用類(lèi)加載器不是用戶(hù)提前緩存的類(lèi)加載器,那么存入
softClassCache對(duì)象中,即不安全的類(lèi)加載器。
其實(shí)CachedIntrospectionResults對(duì)內(nèi)省的優(yōu)化一個(gè)是緩存PropertyDescriptor對(duì)象,第二點(diǎn)就是若類(lèi)加載器失效時(shí),清除舊緩存。
2. CachedIntrospectionResults防止內(nèi)存泄漏
清除給定類(lèi)加載器的內(nèi)省緩存,刪除該類(lèi)加載器下所有類(lèi)的內(nèi)省結(jié)果。以及從acceptedClassLoaders列表中移除類(lèi)加載器(及其子類(lèi))。
//源碼:org.springframework.beans.CachedIntrospectionResults#clearClassLoader清除緩存和類(lèi)加載器
public static void clearClassLoader(@Nullable ClassLoader classLoader) {
acceptedClassLoaders.removeIf(registeredLoader ->
isUnderneathClassLoader(registeredLoader, classLoader));
strongClassCache.keySet().removeIf(beanClass ->
isUnderneathClassLoader(beanClass.getClassLoader(), classLoader));
softClassCache.keySet().removeIf(beanClass ->
isUnderneathClassLoader(beanClass.getClassLoader(), classLoader));
}
這個(gè)方法被誰(shuí)調(diào)用呢?

這個(gè)清除方法的調(diào)用者實(shí)際上就存在兩處:
- Spring初始化時(shí)
org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
try {
//初始化Spring容器
...
}
catch (BeansException ex) {
...
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
在容器初始化時(shí),會(huì)重置introspection 緩存,因?yàn)榭赡懿辉傩枰獑卫齜ean的元數(shù)據(jù)。
- Servlet監(jiān)聽(tīng)中使用
public class IntrospectorCleanupListener implements ServletContextListener {
//web 容器初始化時(shí)(在filter、servlets初始化之前)執(zhí)行
@Override
public void contextInitialized(ServletContextEvent event) {
CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader());
}
//在ServletContext銷(xiāo)毀時(shí)(filters和servlets銷(xiāo)毀執(zhí)之后)執(zhí)行
@Override
public void contextDestroyed(ServletContextEvent event) {
CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader());
Introspector.flushCaches();
}
}
IntrospectorCleanupListener實(shí)現(xiàn)了ServletContextListener接口。雖然使用Spring本身時(shí)不需要使用該監(jiān)聽(tīng)器,因?yàn)镾pring自己的內(nèi)部機(jī)制會(huì)清空對(duì)應(yīng)的緩存。但是如果Spring配合其他框架使用,而其他框架存在這個(gè)問(wèn)題時(shí),例如Struts 和Quartz,那就需要配置這個(gè)監(jiān)聽(tīng)器,在銷(xiāo)毀ServletContext的時(shí)候清除對(duì)應(yīng)緩存。
需要注意,即使存在一個(gè)Introspector造成內(nèi)存泄漏也會(huì)導(dǎo)致整個(gè)應(yīng)用的類(lèi)加載器不會(huì)被垃圾回收器回收,可能會(huì)造成內(nèi)存泄漏。
配置IntrospectorCleanupListener
@Configuration
public class ServletListenerInfo {
@Bean
public ServletListenerRegistrationBean<IntrospectorCleanupListener> IntrospectorCleanupListener() {
ServletListenerRegistrationBean<IntrospectorCleanupListener> li = new ServletListenerRegistrationBean<>();
li.setOrder(0);
li.setListener(new IntrospectorCleanupListener());
return li;
}
}
需要注意的是:IntrospectorCleanupListener優(yōu)先級(jí)應(yīng)該最高。那么它的contextDestroyed方法將會(huì)最后一個(gè)執(zhí)行,將會(huì)發(fā)揮最有效緩存清除的作用。