成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

來自平行世界的救贖

神級的拷問來了

來自平行世界的救贖
為什么要說是救贖呢?先跟各位討論個“死亡問題”,如果你的女票或者你老婆問你,“我跟你媽落水了,你先救誰?”
來自平行世界的救贖
哈哈,沒錯,就是這個來之中國的古老聲音,這個拷問你內(nèi)心的世紀難題!怕了沒?
可以拋硬幣,也可以找個漁網(wǎng)一次性撈起來,可是等等,在這緊急關(guān)頭你真的有這么多時間?
此時的你肯定最想變成超人,或者修得絕世秘法“分身術(shù)”,這樣就不用做這道艱難的選擇題了。
平行宇宙論告訴我們,這世界有無數(shù)個copy,你也有無數(shù)個copy,只要找另外一個世界借一個你過來,你的內(nèi)心就能得到神圣的救贖了。
來自平行世界的救贖

讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:空間域名、網(wǎng)絡(luò)空間、營銷軟件、網(wǎng)站建設(shè)、渭濱網(wǎng)站維護、網(wǎng)站推廣。


怎么做

好了,假設(shè)真的由一個平行世界,為了保證這個方法落地的可行性,我們還需要保證

  • 平行世界的真的是我

    我代表著不僅僅是名字外貌,還有我的人生種種組成才是完整的我,最佳結(jié)果是什么?平行世界的我就是從這一刻跟我分離出來的,是我的真正copy,通一個版本的copy!

  • 另一個我能到這個世界來幫我救一個人

    這是我們討論這個問題的本質(zhì),解決不了的話,再來100個平行世界的我又能怎么樣?所以他要能干涉到我們這個世界,能在這個世界行動!可是既然是平行世界,那么肯定是無法過來的啊,怎么辦呢?大家都知道,作為高緯度的神可以投影到低緯度的世界中,通過“投影”來行動,或許我們可以這么干?

整理下思路
來自平行世界的救贖

兩個同樣的我同時救了兩個生命中最重要的人,不踩坑不扎心,再來幾個老婆都救得了,簡直完美!


圣者(程序猿)時間

解決哲學問題而內(nèi)心得到升華的我們,此時回歸真我(現(xiàn)實),利用僅存的圣者模式思考這個方法的現(xiàn)實意義。
“高大上”的工程師職業(yè)過程中,我們會遇上前人有意或者無意在代碼中留下的坑,譬如

  • 某些設(shè)計得很不合理的單例模式,這讓我們在一個JVM中只有一個實例存在
  • 將某些數(shù)據(jù)(如狀態(tài))存在靜態(tài)字段中,如果修改可能導致運行出錯
  • 或者其它蛋疼不考慮后來人的設(shè)計

但我們需要為這類對象創(chuàng)建全新一個實例去拯救世界時,除了內(nèi)心被千萬草泥馬踐踏而過之外,似乎只能感受到這世界滿滿的惡意了。
不,肯定不是!
我們可是在圣者模式??!
在操蛋的現(xiàn)實社會中我們只是屌絲,但在0和1的世界里,我們可是神!
無所不能的神!
來自平行世界的救贖
神愛世人,怎么會讓自己的羔羊生活在水深火熱中呢?
就像拯救你媽你老婆和你內(nèi)心那樣,我們可以創(chuàng)造出一個平行世界出來啊,從虛無中造物不就是我們的本能么?


設(shè)計

前面我們已經(jīng)討論過世紀難題的解決方案,也給出了設(shè)計圖,此時的我們只要把這個思維轉(zhuǎn)換為由0和1組成的另一個世界的方式表達,似乎就可以了?
來自平行世界的救贖
我們要通過救世主對象去操作一堆“待拯救的對象”,嗯,這就是救世主應(yīng)該做的。
但是,另外一邊出現(xiàn)災(zāi)難了,又有一堆“待拯救的對象”排排坐,等著救世主來拯救。
救世主說,臥槽,我TM分身乏術(shù)啊,上帝沒給我分身這個超能力,我也很無助啊。
來自平行世界的救贖
好了,這個時候就是英雄閃亮登場的機會啦。
來自平行世界的救贖
你爹媽不給你分身術(shù),咱不分身啦,咱直接開一個新的世界,拉一個過來唄,別問為啥,就是這么任性。
來自平行世界的救贖
嗯,具體操作就像如何把大象放進冰箱一樣分3步

1、新開辟一個世界;
2、復(fù)制一個救世主過去;
3、把救世主投影過來;

步驟有啦,分析下怎么執(zhí)行。

  1. 新開辟一個世界

    我們是務(wù)實的工程師,不能吹逼,所以不應(yīng)該叫新開辟世界,應(yīng)該叫做制作一個相對比較隔離的環(huán)境出來,要求呢?這個環(huán)境應(yīng)該

    • 工作在里面的對象跟外面的能力應(yīng)該是完全一樣的
    • 環(huán)境外面應(yīng)該是無法感知里面的情況的
    • 環(huán)境內(nèi)外的對象應(yīng)該是完全不同的

    我們暫且為這個環(huán)境命名為“沙箱”(Sandbox)吧。
    以單例設(shè)計為參考,單例設(shè)計一般是寄托于類(Class)存在的,為了復(fù)制這個對象,我們需要做的是將整個Class復(fù)制一份。
    來自平行世界的救贖
    我們知道Java中的Class是由ClassLoader裝載進內(nèi)存的,而ClassLoader采用的是雙親委派機制,一個ClassLoader內(nèi)獨有的業(yè)務(wù)對象對其它ClassLoader是不存在的,這不就完美滿足我們上面說的三個點嗎?Good,就它了!
    方案:采用ClassLoader作為沙箱環(huán)境隔離

  2. 復(fù)制一個救世主過去

    前面我們確定了ClassLoader方案后思路自然豁然開朗,現(xiàn)在考慮將Class復(fù)制進沙箱(ClassLoader)內(nèi)就非常簡單啦!
    我們知道,ClassLoader裝載Class時候其實是讀取.class文件,再通過ClassLoader的defineClass來實際定義一個類的,嗯,那我們將沙箱外的類定義復(fù)制過來也可以這樣,兩步
    首先讀取.class內(nèi)容。這個文件在哪里呢?當jar包被ClassLoader裝入內(nèi)存后,通過getResource就可以將文件數(shù)據(jù)讀取到啦,完美!
    在沙箱內(nèi)定義類。簡單,就一個defineClass,打完收工~
    嘿,別急,小心類重新定義哦,記得記錄下定義過哪些類。
    來自平行世界的救贖

  3. 把救世主投影過來

    對,這也是個問題。
    剛剛我們有說過,不同ClassLoader的獨有業(yè)務(wù)對象對其它ClassLoader而言是不存在的!這就引發(fā)出問題了,外面無法使用里面創(chuàng)造出來的對象實例!
    來自平行世界的救贖
    舉個例子

    BizObject biz = new BizObject(); //OK
    BizObject biz2 = Sandbox.createObject(BizObject.class); //出錯

    為什么出錯呢?因為沙箱內(nèi)外的BizObject是不一樣的啊,正反粒子在一起會湮滅的。。。
    所以我們需要投影。
    好吧,不是投影,我們需要有一個代理,在沙箱外培養(yǎng)一個傀儡,哦不是,是代理,對這個代理的所有操作都能反饋到沙箱內(nèi)去執(zhí)行。
    來自平行世界的救贖

嗯,到這里為止,我們基本將問題梳理一遍了,那么下一步。。。。。。
來自平行世界的救贖


神說,要有光

通過上面分析和梳理,我們基本已經(jīng)確定了方向和邏輯,現(xiàn)在呢,萬事俱備,只缺一道神奇的東風我們就可以進入全新世界里了,那我們開始擼代碼!
來自平行世界的救贖
等等這位同學,我們是不是漏了什么?
擼代碼前我們先要進行設(shè)計?。?br/>來自平行世界的救贖
好吧,我們討論下本次需求。。。
首先,我們假定了已經(jīng)設(shè)定了一個神奇的“沙箱”,沙箱內(nèi)外隔離,所以內(nèi)外的通信只能通過一座也是非常神奇的橋梁來進行,這就是“代理”;
當外部的某位同學需要創(chuàng)建一個對象但又受到各種限制的時候,他可以在沙箱內(nèi)創(chuàng)建一個此對象的分身,然后通過分身的代理進行操作就可以實現(xiàn)對分身的操縱,從而達成目的。
嗯,需求只有這么多,接下來我們談?wù)勗O(shè)計。
上面討論中我們決定了使用ClassLoader對沙箱內(nèi)外進行隔離,可是不是直接暴露ClassLoader接口給外部使用呢?
ClassLoader能對底層類進行操作,雖然功能強大,但操作復(fù)雜度高,一不留神容易出現(xiàn)問題,所以我們應(yīng)該對它進行封裝,僅提供我們期望用戶去使用的接口,而且我們認為它應(yīng)該具備這些特點

  • 功能單一
  • 與沙箱不相干的都不要暴露
  • 創(chuàng)建對象后直接可以使用

這對ClassLoader來說有些強人所難,所以我們需要把它隱藏起來,創(chuàng)造一個沙箱對外提供服務(wù),而將ClassLoader隱藏在沙箱內(nèi)部,假定它叫“SandboxClassLoader”。
這樣我們就有了

  • 調(diào)用者
  • 沙箱
  • SandboxClassLoader
  • 外部ClassLoader

四個對象了。
還有一點,上面說過我們的調(diào)用者通過代理對沙箱內(nèi)對象進行操作,還記得為什么要使用代理嗎?使用代理的本質(zhì)原因是沙箱內(nèi)外的類分屬不同ClassLoader,即使同名類也是不同的!
同樣道理,當我們通過代理對象進行調(diào)用時,參數(shù)傳遞使用的是沙箱外的對象,進入沙箱內(nèi)也是不能直接使用的,因此,我們同樣需要對這類對象進行轉(zhuǎn)換。
此處我們僅考慮值對象參數(shù),各位同學如果關(guān)心其它對象傳參的話,需要進行類似的代理轉(zhuǎn)換,但值對象的話,我們只要進行值復(fù)制就行了,無需太過復(fù)雜處理
我們通過一幅圖來說明下這個關(guān)系
來自平行世界的救贖
圖片很直觀,就不再重復(fù)解說啦
嗯,基本梳理應(yīng)該已經(jīng)非常清晰了,圖中只有藍色的“沙箱內(nèi)某對象”屬于工作在沙箱內(nèi),動態(tài)創(chuàng)建出來的,其它都是在沙箱外;
而方框畫出了沙箱組件邊界,調(diào)用者和APPClassLoader都屬于已存在的實例無需關(guān)心,組件內(nèi)部就屬于需要實現(xiàn)的部分了。
列一下關(guān)鍵幾個類
來自平行世界的救贖
可以看出,Sandbox的API已經(jīng)變得非常單一和簡單了。
為了簡化設(shè)計,這里規(guī)定了待創(chuàng)建的對象必須有無參構(gòu)造函數(shù),如果同學有需要通過有參構(gòu)造函數(shù)構(gòu)造對象的話,可以進行擴展實現(xiàn),歡迎一起做好這個沙箱工具
為什么這里要分開枚舉和非枚舉對象呢?有同學清楚嗎?
枚舉的概念是指能有限列舉出來的東西,在java中,枚舉對象繼承自Enum,不能通過new方法進行構(gòu)造,只能從枚舉的值中選取
而對象繼承自O(shè)bject,大家都非常的熟悉

創(chuàng)世紀

終于進入最重要的擼代碼環(huán)節(jié)了。。。
來自平行世界的救贖
挑重點的代碼出來,咱擼一擼

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è)計有參構(gòu)造方法呢?
實際使用中,我們會考慮某些類之間內(nèi)聚,當一個類放在沙箱內(nèi)運行時,其它也建議放在沙箱內(nèi)跑,而我們學過“單一性原則”,知道一個包內(nèi)一般都是比較內(nèi)聚的,所以這里設(shè)計就是指定某些package路徑,沙箱將會對這些包內(nèi)對象進行接管。
對于不在這些包內(nèi)的類,如果我們調(diào)用了沙箱來構(gòu)造會怎么樣呢?所謂“Talk is cheap, show me the code”~~
請稍后,我們繼續(xù)構(gòu)造函數(shù),哈哈~~這個問題我們標記為問題1稍后討論
這里出現(xiàn)了SandboxClassLoader,使用了getContextClassLoader()作為參數(shù)傳遞,此處做了什么呢?我們先看看SandboxClassLoader的構(gòu)造方法

    /**
     * 沙箱隔離核心
     *
     * 通過ClassLoader將進行類級別的運行時隔離
     *
     * 此類本質(zhì)上是代理了currentContextClassLoader對象,并增加了對部分需要在沙箱內(nèi)運行的類處理能力
     */
    class SandboxClassLoader extends ClassLoader{
        //當前上下文的ClassLoader,用于尋找類實例并克隆進沙箱
        private final ClassLoader contextClassLoader;
        //緩存已經(jīng)創(chuàng)建過的Class實例,避免重復(fù)定義
        private final Map<String, Class> cache = Maps.newHashMap();

        SandboxClassLoader(ClassLoader contextClassLoader) {
            this.contextClassLoader = contextClassLoader;
        }
        //......
    }

SandboxClassLoader的構(gòu)造方法僅僅是將傳入的contextClassLoader進行暫存?zhèn)溆?,那么我們還是看看getContextClassLoader方法

    /**
     * 獲取當前上下文的類裝載器
     *
     * 此類裝載器需包含MQClient相關(guān)類定義
     * PS:單獨定義為一個方法,是擔心當這個上下文類裝載器滿足不了要求時可以快速更換
     * @return 當前類裝載器
     */
    private ClassLoader getContextClassLoader() {
        //從類裝載器機制而言,線程上下文的類轉(zhuǎn)載器是最符合要求的
        return Thread.currentThread().getContextClassLoader();
    }

好簡單??!
其實這里是有一些設(shè)計依據(jù)的:我們要去創(chuàng)建一個對象,那么這個對象的類定義必然在當前代碼可訪問的。
基于這個考慮,我們可以確定,當用戶使用類似A a = Sandbox.createObject(A.class)進行創(chuàng)建沙箱內(nèi)對象時,A類在這段代碼執(zhí)行的上下文必然可以訪問,此時我們可以通過此上下文的ClassLoader去獲取到這個A類對應(yīng)的.class資源文件,然后重定義該類了。
繼續(xù)看看相關(guān)代碼,為了閱讀方便,我重新組織了下代碼結(jié)構(gòu)

public class Sandbox {
    private SandboxClassLoader classLoader;
    //......

    /**
     * 在沙箱內(nèi)創(chuàng)建指定名稱的類實例
     *
     * 如該名稱類不屬于redefinedPackages所指定的包內(nèi),則直接返回外部類實例
     * @param clzName 待創(chuàng)建實例的類名稱
     * @return 指定類名稱的實例對象
     */
    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的實例
     * @param clz 待創(chuàng)建實例的Class
     * @return 跟clz功能相同并工作在沙箱內(nèi)的類實例
     */
    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)建沙箱外的對象實例,并將其所有方法調(diào)用通過代理轉(zhuǎn)發(fā)到沙箱內(nèi)執(zhí)行
                            另外,由于沙箱內(nèi)外的所有實例都屬于不同的類,因此,對于參數(shù)和返回值還需要進行對象轉(zhuǎn)換,將沙箱內(nèi)外的對象進行對等克隆
             */

            //通過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ù)進行克隆,轉(zhuǎn)換為沙箱內(nèi)對象
                Object[] targetArgs = args == null ? null : util.cloneTo(args, classLoader);
                Object result = targetMethod.invoke(objectInSandbox, targetArgs);
                //調(diào)用后續(xù)對結(jié)果進行克隆,轉(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)方法無具體實現(xiàn),僅進行參數(shù)clzName的校驗,然后就轉(zhuǎn)給T createObject(Class clz),因此主要分析這個方法。
其實代碼量不多(僅19行還包括各種花括號),主要都是注釋,脈絡(luò)如下

  1. 先獲取參數(shù)clz在沙箱內(nèi)的對于類定義clzInSandbox,并通過clzInSandboxnewInstance創(chuàng)建該類的一個具體實例objectInSandbox;因此這里要求clz有無參構(gòu)造函數(shù)
  2. 判斷clzInSandbox是否運行在沙箱內(nèi),如果不是運行在沙箱內(nèi)的話,無需創(chuàng)建代理直接將對象objectInSandbox返回;
    為什么要做這個判斷嗯?這里可以順帶解答前面的問題1了,從代碼

    //如果對象的類裝載器和clz的類裝載器一致,說明不是需要工作在沙箱內(nèi)的對象,直接返回即可,無需代理
    if(objectInSandbox.getClass().getClassLoader() == &gt; clz.getClassLoader()){
        return (T) objectInSandbox;
    }

    我們可以看出來,當創(chuàng)建出來的objectInSandbox也是運行在外部的ClassLoader時,其實是不去創(chuàng)建代理的,因為它就是一個沙箱外的對象,又何必去創(chuàng)建代理這么多此一舉呢?
    可我們明明調(diào)用的是classLoader.loadClass(clz.getName())去取得沙箱內(nèi)的類定義,為什么得到的卻是沙箱外的呢?這跟我們對SandboxClassLoader這個類的設(shè)計是否矛盾了呢?
    好,去看看對應(yīng)的代碼,show me the code

    class SandboxClassLoader extends ClassLoader{
        //當前上下文的ClassLoader,用于尋找類實例并克隆進沙箱
        private final ClassLoader contextClassLoader;
        //......  
    
        /**
         * 覆蓋父類的轉(zhuǎn)載類進內(nèi)存的方法
         * @param name 指定類名稱
         * @return 已轉(zhuǎn)載進內(nèi)存的Class實例
         * @throws ClassNotFoundException
         */
        @Override
        public Class&lt;?&gt; loadClass(String name) throws ClassNotFoundException {
            return findClass(name);
        }
    
        /**
         * 重定義類轉(zhuǎn)載邏輯
         *
         * 1、對于需要運行在沙箱內(nèi)的類(redefinedPackages中聲明),通過復(fù)制contextClassLoader類定義的方式,直接運行在此ClassLoader下
         * 2、對于不需要運行在沙箱內(nèi)的類,直接返回上下文類定義,以減少資源占用
         * @param name 類名稱(全路徑)
         * @return 類定義
         */
        @Override
        protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException {
            if(isRedefinedClass(name)) {
                return getSandboxClass(name);
            } else {
                return contextClassLoader.loadClass(name);
            }
        }
    
        //......
    }

    看得出實際實現(xiàn)邏輯的代碼是findClass方法,僅幾句而已,翻譯過來就是“需要重定義的類我們從沙箱內(nèi)取得,不需要的直接從外部取”,所以會有對象的ClassLoader是外部的。
    那什么是“需要重定義的類”呢?

    /**
     * 是否需要運行在沙箱內(nèi)的類
     * @param name 類名稱
     */
    boolean isRedefinedClass(String name) {
        //校驗是否沙箱約定的需要重定義的包
        for (String redefinedPackage : redefinedPackages) {
            if(name.startsWith(redefinedPackage)){
                return true;
            }
        }
        return false;
    }

    只要是Sandbox類構(gòu)造時指定的包下面的類,統(tǒng)統(tǒng)都屬于需要重新在SandboxClassLoader中重定義的。

  3. 利用cglib庫創(chuàng)建objectInSandbox的代理對象,攔截該代理對象的所有方法執(zhí)行,全部轉(zhuǎn)去實際的對象objectInSandbox中執(zhí)行;
    cglib創(chuàng)建對象的代碼不分析了,本質(zhì)就是通過創(chuàng)建一個指定類的子類對方法進行攔截的過程;
    我們關(guān)心的應(yīng)該是攔截器干了什么?

    enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -&gt; {
                Method targetMethod = clzInSandbox.getMethod(method.getName(), method.getParameterTypes());
                //調(diào)用前需對參數(shù)進行克隆,轉(zhuǎn)換為沙箱內(nèi)對象
                Object[] targetArgs = args == null ? null : util.cloneTo(args, classLoader);
                Object result = targetMethod.invoke(objectInSandbox, targetArgs);
                //調(diào)用后續(xù)對結(jié)果進行克隆,轉(zhuǎn)換為沙箱外對象
                return util.cloneTo(result, getContextClassLoader());
            });

    我們會從沙箱內(nèi)的對象中取得同名同參的方法,然后將參數(shù)進行轉(zhuǎn)換到沙箱內(nèi),再執(zhí)行沙箱內(nèi)對象方法并得到結(jié)果,最后還要將結(jié)果進行轉(zhuǎn)換到沙箱外對象才返回;
    邏輯非常清晰,但沙箱內(nèi)外對象如何轉(zhuǎn)換呢?
    這里代碼有些長且無聊就不單獨貼出來了,有興趣的同學可以上github上自行下載,大體邏輯如下

    1. 判斷對象是否需要轉(zhuǎn)換成沙箱內(nèi)/外,不需要則返回此對象,需要就轉(zhuǎn)2;
    2. 創(chuàng)建沙箱內(nèi)/外對應(yīng)的對象實例;
    3. 遍歷該對象實例的每一個字段,對該字段執(zhí)行步驟1,并將復(fù)制后的值賦值給新對象中對應(yīng)字段;

    嗯,就是這樣。
    前面我們有提到,我們假定傳參對象都是值對象,所以這里的設(shè)計相對簡單,如有哪位同學需要傳非值對象,那么就需要對外部對象做代理

  4. 將代理對象返回;

有些同學關(guān)心類如何從沙箱外復(fù)制到沙箱內(nèi)重定義的是吧?這是SandboxClassLoader的核心部分,展示下代碼邏輯

class SandboxClassLoader extends ClassLoader {
    //......
    //緩存已經(jīng)創(chuàng)建過的Class實例,避免重復(fù)定義
    private final Map<String, Class> cache = Maps.newHashMap();

    /**
        * 內(nèi)部方法:獲取需要在沙箱內(nèi)運行的Class實例
        * @param name 類名稱
        * @return 沙箱內(nèi)的類實例
        * @throws ClassNotFoundException
        */
    private synchronized Class<?> getSandboxClass(String name) throws ClassNotFoundException {
        //1、先從緩存中查找是否已經(jīng)轉(zhuǎn)載過該類,有則直接返回
        if(cache.containsKey(name)){
            return cache.get(name);
        }
        //2、緩存不存在該類時,從currentContextClassLoader中復(fù)制一份到當前緩存中
        Class<?> clz = copyClass(name);
        cache.put(name, clz);
        return clz;
    }

    /**
        * 從currentContextClassLoader中復(fù)制一份類到本ClassLoader中
        *
        * 此復(fù)制是將字節(jié)碼copy到當前ClassLoader進行定義,因此與sandbox外部的Class已經(jīng)完全不同實例,不能給外部直接賦值
        * @param name 待復(fù)制的類名稱
        * @return 工作在當前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);
        }
    }

    //......
}

涉及到的方法主要有兩個,getSandboxClass方法主要負責獲取對象時進行緩存層面的校驗,緩存的目的一個是加速獲取類定義的性能,一個是避免同一個類定義重復(fù)多次執(zhí)行導致出錯。
copyClass顧名思義就是復(fù)制類定義,是從contextClassLoader中將類對應(yīng)的.class文件進行復(fù)制,并在SandboxClassLoader中defineClass的過程,具體請閱讀代碼。

Sandbox中我們還有一個getEnumValue方法,過程有些類似就不重復(fù)介紹,請下載代碼閱讀。

至此,我們完成了代碼的編寫了。
至此,我們完成了新世界的構(gòu)建了!
至此,我們完成了所有工作了?。???
高興得太早了。。。
來自平行世界的救贖


到來的救贖

測試是代碼質(zhì)量的保障,是設(shè)計的保障,是運行的保障,是......的保障,總之,就是保障。
所以,我們還要通過測試,為我們的“世界”進行驗證,看看它是否跟我們預(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);
    }
}

運行結(jié)果
來自平行世界的救贖
OK,測試通過~~~
來自平行世界的救贖


世界的坐標

  • -> github
  • -> 碼云gitee

落地案例:如何在同一個Java進程中連接多個RocketMQ服務(wù)器

名稱欄目:來自平行世界的救贖
URL網(wǎng)址:http://jinyejixie.com/article4/poscie.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、響應(yīng)式網(wǎng)站動態(tài)網(wǎng)站、品牌網(wǎng)站設(shè)計營銷型網(wǎng)站建設(shè)、Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
牙克石市| 东乡| 武强县| 慈溪市| 长沙县| 永登县| 邵阳市| 德化县| SHOW| 高雄市| 纳雍县| 新安县| 祁门县| 广南县| 密山市| 永城市| 富蕴县| 鄂托克前旗| 宜良县| 宝应县| 乐平市| 吴川市| 望城县| 临湘市| 潍坊市| 宁陵县| 汶上县| 崇明县| 双城市| 玉门市| 田东县| 巴东县| 泗水县| 阜城县| 雅江县| 湘潭县| 繁峙县| 武定县| 潜山县| 延边| 建瓯市|