您好,登錄后才能下訂單哦!
為什么要說是救贖呢?先跟各位討論個(gè)“死亡問題”,如果你的女票或者你老婆問你,“我跟你媽落水了,你先救誰?”
哈哈,沒錯(cuò),就是這個(gè)來之中國的古老聲音,這個(gè)拷問你內(nèi)心的世紀(jì)難題!怕了沒?
可以拋硬幣,也可以找個(gè)漁網(wǎng)一次性撈起來,可是等等,在這緊急關(guān)頭你真的有這么多時(shí)間?
此時(shí)的你肯定最想變成超人,或者修得絕世秘法“分身術(shù)”,這樣就不用做這道艱難的選擇題了。
平行宇宙論告訴我們,這世界有無數(shù)個(gè)copy,你也有無數(shù)個(gè)copy,只要找另外一個(gè)世界借一個(gè)你過來,你的內(nèi)心就能得到神圣的救贖了。
好了,假設(shè)真的由一個(gè)平行世界,為了保證這個(gè)方法落地的可行性,我們還需要保證
我代表著不僅僅是名字外貌,還有我的人生種種組成才是完整的我,最佳結(jié)果是什么?平行世界的我就是從這一刻跟我分離出來的,是我的真正copy,通一個(gè)版本的copy!
這是我們討論這個(gè)問題的本質(zhì),解決不了的話,再來100個(gè)平行世界的我又能怎么樣?所以他要能干涉到我們這個(gè)世界,能在這個(gè)世界行動!可是既然是平行世界,那么肯定是無法過來的啊,怎么辦呢?大家都知道,作為高緯度的神可以投影到低緯度的世界中,通過“投影”來行動,或許我們可以這么干?
整理下思路
兩個(gè)同樣的我同時(shí)救了兩個(gè)生命中最重要的人,不踩坑不扎心,再來幾個(gè)老婆都救得了,簡直完美!
解決哲學(xué)問題而內(nèi)心得到升華的我們,此時(shí)回歸真我(現(xiàn)實(shí)),利用僅存的圣者模式思考這個(gè)方法的現(xiàn)實(shí)意義。
“高大上”的工程師職業(yè)過程中,我們會遇上前人有意或者無意在代碼中留下的坑,譬如
但我們需要為這類對象創(chuàng)建全新一個(gè)實(shí)例去拯救世界時(shí),除了內(nèi)心被千萬草泥馬踐踏而過之外,似乎只能感受到這世界滿滿的惡意了。
不,肯定不是!
我們可是在圣者模式!!
在操蛋的現(xiàn)實(shí)社會中我們只是屌絲,但在0和1的世界里,我們可是神!
無所不能的神!
神愛世人,怎么會讓自己的羔羊生活在水深火熱中呢?
就像拯救你媽你老婆和你內(nèi)心那樣,我們可以創(chuàng)造出一個(gè)平行世界出來啊,從虛無中造物不就是我們的本能么?
前面我們已經(jīng)討論過世紀(jì)難題的解決方案,也給出了設(shè)計(jì)圖,此時(shí)的我們只要把這個(gè)思維轉(zhuǎn)換為由0和1組成的另一個(gè)世界的方式表達(dá),似乎就可以了?
我們要通過救世主對象去操作一堆“待拯救的對象”,嗯,這就是救世主應(yīng)該做的。
但是,另外一邊出現(xiàn)災(zāi)難了,又有一堆“待拯救的對象”排排坐,等著救世主來拯救。
救世主說,臥槽,我TM分身乏術(shù)啊,上帝沒給我分身這個(gè)超能力,我也很無助啊。
好了,這個(gè)時(shí)候就是英雄閃亮登場的機(jī)會啦。
你爹媽不給你分身術(shù),咱不分身啦,咱直接開一個(gè)新的世界,拉一個(gè)過來唄,別問為啥,就是這么任性。
嗯,具體操作就像如何把大象放進(jìn)冰箱一樣分3步
1、新開辟一個(gè)世界;
2、復(fù)制一個(gè)救世主過去;
3、把救世主投影過來;
步驟有啦,分析下怎么執(zhí)行。
我們是務(wù)實(shí)的工程師,不能吹逼,所以不應(yīng)該叫新開辟世界,應(yīng)該叫做制作一個(gè)相對比較隔離的環(huán)境出來,要求呢?這個(gè)環(huán)境應(yīng)該
- 工作在里面的對象跟外面的能力應(yīng)該是完全一樣的
- 環(huán)境外面應(yīng)該是無法感知里面的情況的
- 環(huán)境內(nèi)外的對象應(yīng)該是完全不同的
我們暫且為這個(gè)環(huán)境命名為“沙箱”(Sandbox)吧。
以單例設(shè)計(jì)為參考,單例設(shè)計(jì)一般是寄托于類(Class)存在的,為了復(fù)制這個(gè)對象,我們需要做的是將整個(gè)Class復(fù)制一份。
我們知道Java中的Class是由ClassLoader裝載進(jìn)內(nèi)存的,而ClassLoader采用的是雙親委派機(jī)制,一個(gè)ClassLoader內(nèi)獨(dú)有的業(yè)務(wù)對象對其它ClassLoader是不存在的,這不就完美滿足我們上面說的三個(gè)點(diǎn)嗎?Good,就它了!
方案:采用ClassLoader作為沙箱環(huán)境隔離
復(fù)制一個(gè)救世主過去
前面我們確定了ClassLoader方案后思路自然豁然開朗,現(xiàn)在考慮將Class復(fù)制進(jìn)沙箱(ClassLoader)內(nèi)就非常簡單啦!
我們知道,ClassLoader裝載Class時(shí)候其實(shí)是讀取.class文件,再通過ClassLoader的defineClass來實(shí)際定義一個(gè)類的,嗯,那我們將沙箱外的類定義復(fù)制過來也可以這樣,兩步
首先讀取.class內(nèi)容。這個(gè)文件在哪里呢?當(dāng)jar包被ClassLoader裝入內(nèi)存后,通過getResource就可以將文件數(shù)據(jù)讀取到啦,完美!
在沙箱內(nèi)定義類。簡單,就一個(gè)defineClass,打完收工~
嘿,別急,小心類重新定義哦,記得記錄下定義過哪些類。
對,這也是個(gè)問題。
剛剛我們有說過,不同ClassLoader的獨(dú)有業(yè)務(wù)對象對其它ClassLoader而言是不存在的!這就引發(fā)出問題了,外面無法使用里面創(chuàng)造出來的對象實(shí)例!
舉個(gè)例子BizObject biz = new BizObject(); //OK BizObject biz2 = Sandbox.createObject(BizObject.class); //出錯(cuò)
為什么出錯(cuò)呢?因?yàn)樯诚鋬?nèi)外的BizObject是不一樣的啊,正反粒子在一起會湮滅的。。。
所以我們需要投影。
好吧,不是投影,我們需要有一個(gè)代理,在沙箱外培養(yǎng)一個(gè)傀儡,哦不是,是代理,對這個(gè)代理的所有操作都能反饋到沙箱內(nèi)去執(zhí)行。
嗯,到這里為止,我們基本將問題梳理一遍了,那么下一步。。。。。。
通過上面分析和梳理,我們基本已經(jīng)確定了方向和邏輯,現(xiàn)在呢,萬事俱備,只缺一道神奇的東風(fēng)我們就可以進(jìn)入全新世界里了,那我們開始擼代碼!
等等這位同學(xué),我們是不是漏了什么?
擼代碼前我們先要進(jìn)行設(shè)計(jì)??!
好吧,我們討論下本次需求。。。
首先,我們假定了已經(jīng)設(shè)定了一個(gè)神奇的“沙箱”,沙箱內(nèi)外隔離,所以內(nèi)外的通信只能通過一座也是非常神奇的橋梁來進(jìn)行,這就是“代理”;
當(dāng)外部的某位同學(xué)需要創(chuàng)建一個(gè)對象但又受到各種限制的時(shí)候,他可以在沙箱內(nèi)創(chuàng)建一個(gè)此對象的分身,然后通過分身的代理進(jìn)行操作就可以實(shí)現(xiàn)對分身的操縱,從而達(dá)成目的。
嗯,需求只有這么多,接下來我們談?wù)勗O(shè)計(jì)。
上面討論中我們決定了使用ClassLoader對沙箱內(nèi)外進(jìn)行隔離,可是不是直接暴露ClassLoader接口給外部使用呢?
ClassLoader能對底層類進(jìn)行操作,雖然功能強(qiáng)大,但操作復(fù)雜度高,一不留神容易出現(xiàn)問題,所以我們應(yīng)該對它進(jìn)行封裝,僅提供我們期望用戶去使用的接口,而且我們認(rèn)為它應(yīng)該具備這些特點(diǎn)
這對ClassLoader來說有些強(qiáng)人所難,所以我們需要把它隱藏起來,創(chuàng)造一個(gè)沙箱對外提供服務(wù),而將ClassLoader隱藏在沙箱內(nèi)部,假定它叫“SandboxClassLoader”。
這樣我們就有了
四個(gè)對象了。
還有一點(diǎn),上面說過我們的調(diào)用者通過代理對沙箱內(nèi)對象進(jìn)行操作,還記得為什么要使用代理嗎?使用代理的本質(zhì)原因是沙箱內(nèi)外的類分屬不同ClassLoader,即使同名類也是不同的!
同樣道理,當(dāng)我們通過代理對象進(jìn)行調(diào)用時(shí),參數(shù)傳遞使用的是沙箱外的對象,進(jìn)入沙箱內(nèi)也是不能直接使用的,因此,我們同樣需要對這類對象進(jìn)行轉(zhuǎn)換。
此處我們僅考慮值對象參數(shù),各位同學(xué)如果關(guān)心其它對象傳參的話,需要進(jìn)行類似的代理轉(zhuǎn)換,但值對象的話,我們只要進(jìn)行值復(fù)制就行了,無需太過復(fù)雜處理
我們通過一幅圖來說明下這個(gè)關(guān)系
圖片很直觀,就不再重復(fù)解說啦
嗯,基本梳理應(yīng)該已經(jīng)非常清晰了,圖中只有藍(lán)色的“沙箱內(nèi)某對象”屬于工作在沙箱內(nèi),動態(tài)創(chuàng)建出來的,其它都是在沙箱外;
而方框畫出了沙箱組件邊界,調(diào)用者和APPClassLoader都屬于已存在的實(shí)例無需關(guān)心,組件內(nèi)部就屬于需要實(shí)現(xiàn)的部分了。
列一下關(guān)鍵幾個(gè)類
可以看出,Sandbox的API已經(jīng)變得非常單一和簡單了。
為了簡化設(shè)計(jì),這里規(guī)定了待創(chuàng)建的對象必須有無參構(gòu)造函數(shù),如果同學(xué)有需要通過有參構(gòu)造函數(shù)構(gòu)造對象的話,可以進(jìn)行擴(kuò)展實(shí)現(xiàn),歡迎一起做好這個(gè)沙箱工具
為什么這里要分開枚舉和非枚舉對象呢?有同學(xué)清楚嗎?
枚舉的概念是指能有限列舉出來的東西,在java中,枚舉對象繼承自Enum,不能通過new方法進(jìn)行構(gòu)造,只能從枚舉的值中選取
而對象繼承自O(shè)bject,大家都非常的熟悉
終于進(jìn)入最重要的擼代碼環(huán)節(jié)了。。。
挑重點(diǎn)的代碼出來,咱擼一擼
public class Sandbox {
private SandboxClassLoader classLoader;
private SandboxUtil util = new SandboxUtil();
private List<String> redefinedPackages;
public Sandbox(List<String> packages){
redefinedPackages = packages;
classLoader = new SandboxClassLoader(getContextClassLoader());
}
/**
* 沙箱對象構(gòu)造方法
* @param redefinedPackages 需工作在沙箱內(nèi)的包
* 此包下面所有類都在工作在沙箱內(nèi)
*/
public Sandbox(String... redefinedPackages){
this(Lists.newArrayList(redefinedPackages));
}
// ......
}
先說說構(gòu)造方法。
既然是沙箱對象,為什么要設(shè)計(jì)有參構(gòu)造方法呢?
實(shí)際使用中,我們會考慮某些類之間內(nèi)聚,當(dāng)一個(gè)類放在沙箱內(nèi)運(yùn)行時(shí),其它也建議放在沙箱內(nèi)跑,而我們學(xué)過“單一性原則”,知道一個(gè)包內(nèi)一般都是比較內(nèi)聚的,所以這里設(shè)計(jì)就是指定某些package路徑,沙箱將會對這些包內(nèi)對象進(jìn)行接管。
對于不在這些包內(nèi)的類,如果我們調(diào)用了沙箱來構(gòu)造會怎么樣呢?所謂“Talk is cheap, show me the code”~~
請稍后,我們繼續(xù)構(gòu)造函數(shù),哈哈~~這個(gè)問題我們標(biāo)記為問題1稍后討論
這里出現(xiàn)了SandboxClassLoader,使用了getContextClassLoader()
作為參數(shù)傳遞,此處做了什么呢?我們先看看SandboxClassLoader的構(gòu)造方法
/**
* 沙箱隔離核心
*
* 通過ClassLoader將進(jìn)行類級別的運(yùn)行時(shí)隔離
*
* 此類本質(zhì)上是代理了currentContextClassLoader對象,并增加了對部分需要在沙箱內(nèi)運(yùn)行的類處理能力
*/
class SandboxClassLoader extends ClassLoader{
//當(dāng)前上下文的ClassLoader,用于尋找類實(shí)例并克隆進(jìn)沙箱
private final ClassLoader contextClassLoader;
//緩存已經(jīng)創(chuàng)建過的Class實(shí)例,避免重復(fù)定義
private final Map<String, Class> cache = Maps.newHashMap();
SandboxClassLoader(ClassLoader contextClassLoader) {
this.contextClassLoader = contextClassLoader;
}
//......
}
SandboxClassLoader的構(gòu)造方法僅僅是將傳入的contextClassLoader
進(jìn)行暫存?zhèn)溆茫敲次覀冞€是看看getContextClassLoader
方法
/**
* 獲取當(dāng)前上下文的類裝載器
*
* 此類裝載器需包含MQClient相關(guān)類定義
* PS:單獨(dú)定義為一個(gè)方法,是擔(dān)心當(dāng)這個(gè)上下文類裝載器滿足不了要求時(shí)可以快速更換
* @return 當(dāng)前類裝載器
*/
private ClassLoader getContextClassLoader() {
//從類裝載器機(jī)制而言,線程上下文的類轉(zhuǎn)載器是最符合要求的
return Thread.currentThread().getContextClassLoader();
}
好簡單??!
其實(shí)這里是有一些設(shè)計(jì)依據(jù)的:我們要去創(chuàng)建一個(gè)對象,那么這個(gè)對象的類定義必然在當(dāng)前代碼可訪問的。
基于這個(gè)考慮,我們可以確定,當(dāng)用戶使用類似A a = Sandbox.createObject(A.class)
進(jìn)行創(chuàng)建沙箱內(nèi)對象時(shí),A類在這段代碼執(zhí)行的上下文必然可以訪問,此時(shí)我們可以通過此上下文的ClassLoader去獲取到這個(gè)A類對應(yīng)的.class資源文件,然后重定義該類了。
繼續(xù)看看相關(guān)代碼,為了閱讀方便,我重新組織了下代碼結(jié)構(gòu)
public class Sandbox {
private SandboxClassLoader classLoader;
//......
/**
* 在沙箱內(nèi)創(chuàng)建指定名稱的類實(shí)例
*
* 如該名稱類不屬于redefinedPackages所指定的包內(nèi),則直接返回外部類實(shí)例
* @param clzName 待創(chuàng)建實(shí)例的類名稱
* @return 指定類名稱的實(shí)例對象
*/
public <T extends Object> T createObject(String clzName) throws ClassNotFoundException, SandboxCannotCreateObjectException {
Class clz = Class.forName(clzName);
return (T) createObject(clz);
}
/**
* 在沙箱內(nèi)創(chuàng)建指定Class的實(shí)例
* @param clz 待創(chuàng)建實(shí)例的Class
* @return 跟clz功能相同并工作在沙箱內(nèi)的類實(shí)例
*/
public synchronized <T extends Object> T createObject(Class<T> clz) throws SandboxCannotCreateObjectException {
try {
final Class<?> clzInSandbox = classLoader.loadClass(clz.getName());
final Object objectInSandbox = clzInSandbox.newInstance();
//如果對象的類裝載器和clz的類裝載器一致,說明不是需要工作在沙箱內(nèi)的對象,直接返回即可,無需代理
if(objectInSandbox.getClass().getClassLoader() == clz.getClassLoader()){
return (T) objectInSandbox;
}
/*
創(chuàng)建生產(chǎn)者的代理:由于沙箱內(nèi)外的對象本質(zhì)上屬于不同的類,因此需要將兩者能力橋接起來
這里采用了代理模式,通過創(chuàng)建沙箱外的對象實(shí)例,并將其所有方法調(diào)用通過代理轉(zhuǎn)發(fā)到沙箱內(nèi)執(zhí)行
另外,由于沙箱內(nèi)外的所有實(shí)例都屬于不同的類,因此,對于參數(shù)和返回值還需要進(jìn)行對象轉(zhuǎn)換,將沙箱內(nèi)外的對象進(jìn)行對等克隆
*/
//通過cglib創(chuàng)建對象的子類代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clz);
enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
Method targetMethod = clzInSandbox.getMethod(method.getName(), method.getParameterTypes());
//調(diào)用前需對參數(shù)進(jìn)行克隆,轉(zhuǎn)換為沙箱內(nèi)對象
Object[] targetArgs = args == null ? null : util.cloneTo(args, classLoader);
Object result = targetMethod.invoke(objectInSandbox, targetArgs);
//調(diào)用后續(xù)對結(jié)果進(jìn)行克隆,轉(zhuǎn)換為沙箱外對象
return util.cloneTo(result, getContextClassLoader());
});
return (T) enhancer.create();
}catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
throw new SandboxCannotCreateObjectException("無法在沙箱內(nèi)創(chuàng)建對象", e);
}
}
//......
}
Sandbox中創(chuàng)建對象的主要方法出現(xiàn)了!為了方便閱讀,我將無關(guān)代碼剔除,僅保留createObject
方法。 T createObject(String clzName)
方法無具體實(shí)現(xiàn),僅進(jìn)行參數(shù)clzName
的校驗(yàn),然后就轉(zhuǎn)給T createObject(Class clz)
,因此主要分析這個(gè)方法。
其實(shí)代碼量不多(僅19行還包括各種花括號),主要都是注釋,脈絡(luò)如下
clz
在沙箱內(nèi)的對于類定義clzInSandbox
,并通過clzInSandbox
的newInstance
創(chuàng)建該類的一個(gè)具體實(shí)例objectInSandbox
;因此這里要求clz
有無參構(gòu)造函數(shù) 判斷clzInSandbox
是否運(yùn)行在沙箱內(nèi),如果不是運(yùn)行在沙箱內(nèi)的話,無需創(chuàng)建代理直接將對象objectInSandbox
返回;
為什么要做這個(gè)判斷嗯?這里可以順帶解答前面的問題1了,從代碼
//如果對象的類裝載器和clz的類裝載器一致,說明不是需要工作在沙箱內(nèi)的對象,直接返回即可,無需代理 if(objectInSandbox.getClass().getClassLoader() == > clz.getClassLoader()){ return (T) objectInSandbox; }
我們可以看出來,當(dāng)創(chuàng)建出來的objectInSandbox
也是運(yùn)行在外部的ClassLoader時(shí),其實(shí)是不去創(chuàng)建代理的,因?yàn)樗褪且粋€(gè)沙箱外的對象,又何必去創(chuàng)建代理這么多此一舉呢?
可我們明明調(diào)用的是classLoader.loadClass(clz.getName())
去取得沙箱內(nèi)的類定義,為什么得到的卻是沙箱外的呢?這跟我們對SandboxClassLoader這個(gè)類的設(shè)計(jì)是否矛盾了呢?
好,去看看對應(yīng)的代碼,show me the code
class SandboxClassLoader extends ClassLoader{ //當(dāng)前上下文的ClassLoader,用于尋找類實(shí)例并克隆進(jìn)沙箱 private final ClassLoader contextClassLoader; //...... /** * 覆蓋父類的轉(zhuǎn)載類進(jìn)內(nèi)存的方法 * @param name 指定類名稱 * @return 已轉(zhuǎn)載進(jìn)內(nèi)存的Class實(shí)例 * @throws ClassNotFoundException */ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return findClass(name); } /** * 重定義類轉(zhuǎn)載邏輯 * * 1、對于需要運(yùn)行在沙箱內(nèi)的類(redefinedPackages中聲明),通過復(fù)制contextClassLoader類定義的方式,直接運(yùn)行在此ClassLoader下 * 2、對于不需要運(yùn)行在沙箱內(nèi)的類,直接返回上下文類定義,以減少資源占用 * @param name 類名稱(全路徑) * @return 類定義 */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if(isRedefinedClass(name)) { return getSandboxClass(name); } else { return contextClassLoader.loadClass(name); } } //...... }
看得出實(shí)際實(shí)現(xiàn)邏輯的代碼是findClass
方法,僅幾句而已,翻譯過來就是“需要重定義的類我們從沙箱內(nèi)取得,不需要的直接從外部取”,所以會有對象的ClassLoader是外部的。
那什么是“需要重定義的類”呢?
/** * 是否需要運(yùn)行在沙箱內(nèi)的類 * @param name 類名稱 */ boolean isRedefinedClass(String name) { //校驗(yàn)是否沙箱約定的需要重定義的包 for (String redefinedPackage : redefinedPackages) { if(name.startsWith(redefinedPackage)){ return true; } } return false; }
只要是Sandbox類構(gòu)造時(shí)指定的包下面的類,統(tǒng)統(tǒng)都屬于需要重新在SandboxClassLoader中重定義的。
利用cglib庫創(chuàng)建objectInSandbox
的代理對象,攔截該代理對象的所有方法執(zhí)行,全部轉(zhuǎn)去實(shí)際的對象objectInSandbox
中執(zhí)行;
cglib創(chuàng)建對象的代碼不分析了,本質(zhì)就是通過創(chuàng)建一個(gè)指定類的子類對方法進(jìn)行攔截的過程;
我們關(guān)心的應(yīng)該是攔截器干了什么?
enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> { Method targetMethod = clzInSandbox.getMethod(method.getName(), method.getParameterTypes()); //調(diào)用前需對參數(shù)進(jìn)行克隆,轉(zhuǎn)換為沙箱內(nèi)對象 Object[] targetArgs = args == null ? null : util.cloneTo(args, classLoader); Object result = targetMethod.invoke(objectInSandbox, targetArgs); //調(diào)用后續(xù)對結(jié)果進(jìn)行克隆,轉(zhuǎn)換為沙箱外對象 return util.cloneTo(result, getContextClassLoader()); });
我們會從沙箱內(nèi)的對象中取得同名同參的方法,然后將參數(shù)進(jìn)行轉(zhuǎn)換到沙箱內(nèi),再執(zhí)行沙箱內(nèi)對象方法并得到結(jié)果,最后還要將結(jié)果進(jìn)行轉(zhuǎn)換到沙箱外對象才返回;
邏輯非常清晰,但沙箱內(nèi)外對象如何轉(zhuǎn)換呢?
這里代碼有些長且無聊就不單獨(dú)貼出來了,有興趣的同學(xué)可以上github上自行下載,大體邏輯如下
- 判斷對象是否需要轉(zhuǎn)換成沙箱內(nèi)/外,不需要則返回此對象,需要就轉(zhuǎn)2;
- 創(chuàng)建沙箱內(nèi)/外對應(yīng)的對象實(shí)例;
- 遍歷該對象實(shí)例的每一個(gè)字段,對該字段執(zhí)行步驟1,并將復(fù)制后的值賦值給新對象中對應(yīng)字段;
嗯,就是這樣。
前面我們有提到,我們假定傳參對象都是值對象,所以這里的設(shè)計(jì)相對簡單,如有哪位同學(xué)需要傳非值對象,那么就需要對外部對象做代理
有些同學(xué)關(guān)心類如何從沙箱外復(fù)制到沙箱內(nèi)重定義的是吧?這是SandboxClassLoader的核心部分,展示下代碼邏輯
class SandboxClassLoader extends ClassLoader {
//......
//緩存已經(jīng)創(chuàng)建過的Class實(shí)例,避免重復(fù)定義
private final Map<String, Class> cache = Maps.newHashMap();
/**
* 內(nèi)部方法:獲取需要在沙箱內(nèi)運(yùn)行的Class實(shí)例
* @param name 類名稱
* @return 沙箱內(nèi)的類實(shí)例
* @throws ClassNotFoundException
*/
private synchronized Class<?> getSandboxClass(String name) throws ClassNotFoundException {
//1、先從緩存中查找是否已經(jīng)轉(zhuǎn)載過該類,有則直接返回
if(cache.containsKey(name)){
return cache.get(name);
}
//2、緩存不存在該類時(shí),從currentContextClassLoader中復(fù)制一份到當(dāng)前緩存中
Class<?> clz = copyClass(name);
cache.put(name, clz);
return clz;
}
/**
* 從currentContextClassLoader中復(fù)制一份類到本ClassLoader中
*
* 此復(fù)制是將字節(jié)碼copy到當(dāng)前ClassLoader進(jìn)行定義,因此與sandbox外部的Class已經(jīng)完全不同實(shí)例,不能給外部直接賦值
* @param name 待復(fù)制的類名稱
* @return 工作在當(dāng)前ClassLoader中的Class
* @throws ClassNotFoundException
*/
private synchronized Class<?> copyClass(String name) throws ClassNotFoundException {
//取得.class文件所在路徑
String path = name.replace('.', '/') + ".class";
//通過上下文類裝載器獲取資源句柄
try (InputStream stream = contextClassLoader.getResourceAsStream(path)) {
if(stream == null) throw new ClassNotFoundException(String.format("找不到類%s", name));
//讀取所有字節(jié)內(nèi)容
byte[] content = readFromStream(stream);
return defineClass(name, content, 0, content.length);
} catch (IOException e) {
throw new ClassNotFoundException("找不到指定的類", e);
}
}
//......
}
涉及到的方法主要有兩個(gè),getSandboxClass
方法主要負(fù)責(zé)獲取對象時(shí)進(jìn)行緩存層面的校驗(yàn),緩存的目的一個(gè)是加速獲取類定義的性能,一個(gè)是避免同一個(gè)類定義重復(fù)多次執(zhí)行導(dǎo)致出錯(cuò)。 copyClass
顧名思義就是復(fù)制類定義,是從contextClassLoader
中將類對應(yīng)的.class文件進(jìn)行復(fù)制,并在SandboxClassLoader中defineClass的過程,具體請閱讀代碼。
Sandbox中我們還有一個(gè)getEnumValue
方法,過程有些類似就不重復(fù)介紹,請下載代碼閱讀。
至此,我們完成了代碼的編寫了。
至此,我們完成了新世界的構(gòu)建了!
至此,我們完成了所有工作了?。???
高興得太早了。。。
測試是代碼質(zhì)量的保障,是設(shè)計(jì)的保障,是運(yùn)行的保障,是......的保障,總之,就是保障。
所以,我們還要通過測試,為我們的“世界”進(jìn)行驗(yàn)證,看看它是否跟我們預(yù)期一致。
這只需要使用單元測試就可以做到了。代碼
public class SandboxTest {
@Test
public void getEnumValue() throws SandboxCannotCreateObjectException {
//設(shè)定重定義的包
Sandbox sandbox = new Sandbox("com.google.common.collect");
//獲取沙箱內(nèi)對象,雖然是同名同值,但由于分屬沙箱內(nèi)外,因此預(yù)期應(yīng)該不等
Enum type = sandbox.getEnumValue(com.google.common.collect.BoundType.CLOSED);
assertNotEquals(type, com.google.common.collect.BoundType.CLOSED);
//通過沙箱獲取非設(shè)定需要重定義的包內(nèi)對象,預(yù)期應(yīng)該是相等
Enum property = sandbox.getEnumValue(com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH);
assertEquals(property, com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH);
}
@Test
public void createObject() throws SandboxCannotCreateObjectException, ClassNotFoundException {
//設(shè)定重定義的包
Sandbox sandbox = new Sandbox("com.google.common.eventbus");
//獲取沙箱內(nèi)對象,預(yù)期中類定義應(yīng)該與沙箱外的類定義不等
com.google.common.eventbus.EventBus bus = sandbox.createObject(com.google.common.eventbus.EventBus.class);
assertNotEquals(bus.getClass(), com.google.common.eventbus.EventBus.class);
//通過名稱獲取,如上
bus = sandbox.createObject("com.google.common.eventbus.EventBus");
assertNotEquals(bus.getClass(), com.google.common.eventbus.EventBus.class);
//通過沙箱獲取無需重定義的類,預(yù)期應(yīng)該跟沙箱外相等
List<String> list = sandbox.createObject(ArrayList.class);
assertEquals(list.getClass(), ArrayList.class);
}
}
運(yùn)行結(jié)果
OK,測試通過~~~
落地案例:如何在同一個(gè)Java進(jìn)程中連接多個(gè)RocketMQ服務(wù)器
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。