網(wǎng)頁兼容性站點(diǎn)app注冊(cè)推廣拉人
前言
在之前的文章中我們將,靜態(tài)方法、構(gòu)造方法、實(shí)例方法的增強(qiáng)邏輯都分析完畢,但在增強(qiáng)前,對(duì)于攔截類的加載是至關(guān)重要的,下面我們就來詳細(xì)的分析
增強(qiáng)插件的加載
- 靜態(tài)方法增強(qiáng)前的加載
//clazz 要修改的字節(jié)碼的原生類
StaticMethodsAroundInterceptor interceptor = InterceptorInstanceLoader.load(staticMethodsAroundInterceptorClassName, clazz.getClassLoader());
- 構(gòu)造/實(shí)例方法前的加載
//當(dāng)前攔截到的類的類加載器
interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);
問題
可以看到靜態(tài)方法增強(qiáng)可以直接通過clazz.getClassLoader()
獲取類加載器,而實(shí)例方法增強(qiáng)需要從上層方法傳遞ClassLoader
,這是為什么?
- 靜態(tài)方法增強(qiáng)直接通過clazz.getClassLoader()獲取類加載器是因?yàn)殪o態(tài)方法直接綁定了類
- 構(gòu)造/實(shí)例方法增強(qiáng)需要從上層傳遞ClassLoader有兩個(gè)原因
- 一份字節(jié)碼可能被多個(gè)ClassLoader加載,這樣加載出來的每個(gè)實(shí)例都不相等,所以必須要綁定好ClassLoader
- 加載插件攔截器可能會(huì)出現(xiàn)無法加載的情況,如果把加載過程放到了
intercept
中,會(huì)和加載失敗的異常和業(yè)務(wù)異常會(huì)混淆在一起,如果放到ConstructorInter
的構(gòu)造方法中進(jìn)行加載,就會(huì)把異常分割開
這個(gè)問題解決了,下面來詳細(xì)加載的過程
InterceptorInstanceLoader
public class InterceptorInstanceLoader {private static ConcurrentHashMap<String, Object> INSTANCE_CACHE = new ConcurrentHashMap<String, Object>();private static ReentrantLock INSTANCE_LOAD_LOCK = new ReentrantLock();/*** key -> 當(dāng)前插件要攔截的這個(gè)目標(biāo)類的類加載器* value -> AgentClassLoader類加載器 作用:能加載當(dāng)前插件,也能加載要攔截的這個(gè)目標(biāo)類* */private static Map<ClassLoader, ClassLoader> EXTEND_PLUGIN_CLASSLOADERS = new HashMap<ClassLoader, ClassLoader>();/*** Load an instance of interceptor, and keep it singleton. Create {@link AgentClassLoader} for each* targetClassLoader, as an extend classloader. It can load interceptor classes from plugins, activations folders.** @param className the interceptor class, which is expected to be found* @param targetClassLoader the class loader for current application context* @param <T> expected type* @return the type reference.*/public static <T> T load(//要進(jìn)行增強(qiáng)邏輯的增強(qiáng)類String className,//當(dāng)前攔截到的類的類加載器ClassLoader targetClassLoader) throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException {if (targetClassLoader == null) {targetClassLoader = InterceptorInstanceLoader.class.getClassLoader();}//舉例說明這個(gè)key值//com.test.MyTest_OF_com.test.classloader.MyTestClassLoader@123String instanceKey = className + "_OF_" + targetClassLoader.getClass().getName() + "@" + Integer.toHexString(targetClassLoader.hashCode());// className所代表的攔截器的實(shí)例 對(duì)于同一個(gè)classloader而言相同的類只加載一次 Object inst = INSTANCE_CACHE.get(instanceKey);if (inst == null) {INSTANCE_LOAD_LOCK.lock();ClassLoader pluginLoader;try {pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader);if (pluginLoader == null) {/*** <===========!!!重點(diǎn)!!!==========>* 這里用AgentClassLoader并把targetClassLoader傳入的原因是* 要進(jìn)行增強(qiáng)邏輯的增強(qiáng)類是由AgentClassLoader進(jìn)行加載的,而要增強(qiáng)的目標(biāo)類不知道是哪個(gè)類加載器。* 但是增強(qiáng)類的邏輯是要在目標(biāo)類中進(jìn)行切入的,這就要求增強(qiáng)類和目標(biāo)類的類加載器必須是同一個(gè)才行。* 所以這里利用了類加載器的雙親委派機(jī)制來進(jìn)行加載,將目標(biāo)類的類加載器作為AgentClassLoader的父類加載器* */pluginLoader = new AgentClassLoader(targetClassLoader);EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader);}} finally {INSTANCE_LOAD_LOCK.unlock();}// 通過pluginLoader來實(shí)例化攔截器對(duì)象inst = Class.forName(className, true, pluginLoader).newInstance();if (inst != null) {INSTANCE_CACHE.put(instanceKey, inst);}}return (T) inst;}
}
總結(jié)
- 對(duì)于每個(gè)插件的增強(qiáng)類都初始化了
AgentClassLoader
來加載增強(qiáng)類 - 將當(dāng)前攔截到的類的類加載器傳入
AgentClassLoader
,作為其父的類加載器
問題1:為什么將當(dāng)前攔截到的類的類加載器傳入AgentClassLoader
,作為其父的類加載器
將targetClassLoader
作為agentClassLoader
的父類加載器,這樣通過雙親委派模型模型,targetClassLoader可以加載應(yīng)用系統(tǒng)中的類
以阿里數(shù)據(jù)源druid舉例:假設(shè)應(yīng)用系統(tǒng)中數(shù)據(jù)源DruidDataSourceStatManager
的類是由AppClassLoader加載的
PoolingAddDruidDataSourceInterceptor
要修改DruidDataSourceStatManager
的字節(jié)碼,兩個(gè)類需要能交互,前提就是PoolingAddDruidDataSourceInterceptor
能通過某種方式訪問到DruidDataSourceStatManager
讓AgentClassLoader
的父類加載器指向加載druid的AppClassLoader
,當(dāng)PoolingAddDruidDataSourceInterceptor
去操作DruidDataSourceStatManager
類時(shí),通過雙親委派機(jī)制,AgentClassLoader
的父類加載器AppClassLoader
能加載到DruidDataSourceStatManager
問題2:為什么每個(gè)插件的增強(qiáng)類都要初始化一個(gè)AgentClassLoader
來加載增強(qiáng)類,不能共用一個(gè)嗎
如果只實(shí)例化一個(gè)AgentClassLoader
實(shí)例,由于應(yīng)用系統(tǒng)中的類不存在于AgentClassLoader
的classpath下,那此時(shí)AgentClassLoader加載不到應(yīng)用系統(tǒng)中的類。
比如說第一個(gè)業(yè)務(wù)類是由BootStrapClassLoader
加載的,第二個(gè)業(yè)務(wù)類是由AppClassLoader
加載的,根據(jù)雙親委派機(jī)制那么第二個(gè)業(yè)務(wù)類增強(qiáng)就會(huì)有問題,因?yàn)樵谝粋€(gè)業(yè)務(wù)類增強(qiáng)時(shí)AgentClassLoader
的父的類加載器已經(jīng)是BootStrapClassLoader
了,是加載不到AppClassLoader
的內(nèi)容的
以上關(guān)于非JDK類庫的靜態(tài)方法、構(gòu)造方法、實(shí)例方法都已經(jīng)分析完畢,后面的文章會(huì)詳細(xì)分析JDK類庫中的類是如何被增強(qiáng)攔截的