中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

來自平行世界的救贖

發布時間:2020-06-25 21:58:16 來源:網絡 閱讀:412 作者:夢莊 欄目:軟件技術

神級的拷問來了

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


怎么做

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

  • 平行世界的真的是我

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

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

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

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

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


圣者(程序猿)時間

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

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

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


設計

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

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

步驟有啦,分析下怎么執行。

  1. 新開辟一個世界

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

    • 工作在里面的對象跟外面的能力應該是完全一樣的
    • 環境外面應該是無法感知里面的情況的
    • 環境內外的對象應該是完全不同的

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

  2. 復制一個救世主過去

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

  3. 把救世主投影過來

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

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

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

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


神說,要有光

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

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

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

  • 調用者
  • 沙箱
  • SandboxClassLoader
  • 外部ClassLoader

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

創世紀

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

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());
    }

    /**
     * 沙箱對象構造方法
     * @param redefinedPackages 需工作在沙箱內的包
     *                          此包下面所有類都在工作在沙箱內
     */
    public Sandbox(String... redefinedPackages){
        this(Lists.newArrayList(redefinedPackages));
    }
    // ......
}

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

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

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

SandboxClassLoader的構造方法僅僅是將傳入的contextClassLoader進行暫存備用,那么我們還是看看getContextClassLoader方法

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

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

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

    /**
     * 在沙箱內創建指定名稱的類實例
     *
     * 如該名稱類不屬于redefinedPackages所指定的包內,則直接返回外部類實例
     * @param clzName 待創建實例的類名稱
     * @return 指定類名稱的實例對象
     */
    public <T extends Object> T createObject(String clzName) throws ClassNotFoundException, SandboxCannotCreateObjectException {
        Class clz = Class.forName(clzName);
        return (T) createObject(clz);
    }

    /**
     * 在沙箱內創建指定Class的實例
     * @param clz 待創建實例的Class
     * @return 跟clz功能相同并工作在沙箱內的類實例
     */
    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的類裝載器一致,說明不是需要工作在沙箱內的對象,直接返回即可,無需代理
            if(objectInSandbox.getClass().getClassLoader() == clz.getClassLoader()){
                return (T) objectInSandbox;
            }

            /*
            創建生產者的代理:由于沙箱內外的對象本質上屬于不同的類,因此需要將兩者能力橋接起來
                            這里采用了代理模式,通過創建沙箱外的對象實例,并將其所有方法調用通過代理轉發到沙箱內執行
                            另外,由于沙箱內外的所有實例都屬于不同的類,因此,對于參數和返回值還需要進行對象轉換,將沙箱內外的對象進行對等克隆
             */

            //通過cglib創建對象的子類代理
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clz);
            enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
                Method targetMethod = clzInSandbox.getMethod(method.getName(), method.getParameterTypes());
                //調用前需對參數進行克隆,轉換為沙箱內對象
                Object[] targetArgs = args == null ? null : util.cloneTo(args, classLoader);
                Object result = targetMethod.invoke(objectInSandbox, targetArgs);
                //調用后續對結果進行克隆,轉換為沙箱外對象
                return util.cloneTo(result, getContextClassLoader());
            });
            return (T) enhancer.create();
        }catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
            throw new SandboxCannotCreateObjectException("無法在沙箱內創建對象", e);
        }
    }

    //......
}

Sandbox中創建對象的主要方法出現了!為了方便閱讀,我將無關代碼剔除,僅保留createObject方法。
T createObject(String clzName)方法無具體實現,僅進行參數clzName的校驗,然后就轉給T createObject(Class clz),因此主要分析這個方法。
其實代碼量不多(僅19行還包括各種花括號),主要都是注釋,脈絡如下

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

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

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

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

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

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

    只要是Sandbox類構造時指定的包下面的類,統統都屬于需要重新在SandboxClassLoader中重定義的。

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

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

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

    1. 判斷對象是否需要轉換成沙箱內/外,不需要則返回此對象,需要就轉2;
    2. 創建沙箱內/外對應的對象實例;
    3. 遍歷該對象實例的每一個字段,對該字段執行步驟1,并將復制后的值賦值給新對象中對應字段;

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

  4. 將代理對象返回;

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

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

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

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

            //讀取所有字節內容
            byte[] content = readFromStream(stream);
            return defineClass(name, content, 0, content.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("找不到指定的類", e);
        }
    }

    //......
}

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

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

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


到來的救贖

測試是代碼質量的保障,是設計的保障,是運行的保障,是......的保障,總之,就是保障。
所以,我們還要通過測試,為我們的“世界”進行驗證,看看它是否跟我們預期一致。
這只需要使用單元測試就可以做到了。代碼

public class SandboxTest {

    @Test
    public void getEnumValue() throws SandboxCannotCreateObjectException {
        //設定重定義的包
        Sandbox sandbox = new Sandbox("com.google.common.collect");

        //獲取沙箱內對象,雖然是同名同值,但由于分屬沙箱內外,因此預期應該不等
        Enum type = sandbox.getEnumValue(com.google.common.collect.BoundType.CLOSED);
        assertNotEquals(type, com.google.common.collect.BoundType.CLOSED);

        //通過沙箱獲取非設定需要重定義的包內對象,預期應該是相等
        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 {
        //設定重定義的包
        Sandbox sandbox = new Sandbox("com.google.common.eventbus");

        //獲取沙箱內對象,預期中類定義應該與沙箱外的類定義不等
        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);

        //通過沙箱獲取無需重定義的類,預期應該跟沙箱外相等
        List<String> list = sandbox.createObject(ArrayList.class);
        assertEquals(list.getClass(), ArrayList.class);
    }
}

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


世界的坐標

  • -> github
  • -> 碼云gitee

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阳新县| 平果县| 德令哈市| 兴安盟| 天台县| 来凤县| 革吉县| 澄江县| 威信县| 得荣县| 闽清县| 商都县| 渭南市| 平遥县| 隆昌县| 城步| 余干县| 拜城县| 平度市| 天津市| 绥宁县| 德阳市| 广河县| 临夏县| 龙胜| 高陵县| 甘孜| 和田县| 琼结县| 珲春市| 肇庆市| 乐至县| 宝山区| 太和县| 子长县| 德江县| 平南县| 三门县| 溆浦县| 武汉市| 高阳县|