簡體   English   中英

Java ClassLoaders - 將 class 轉換為接口

[英]Java ClassLoaders - Cast class to an interface

我正在嘗試開發一個 java 應用程序,用戶可以在其中添加必須實現通用接口的功能(通過插件):PluginFunction。 然后,custom.class 文件必須位於指定目錄(在本例中為 ./plugins)並由我的自定義類加載器加載。

從 IDE 進行測試時,一切正常,但是當我將應用程序導出到 jar 文件時,會引發以下異常:

java.lang.ClassCastException: class operaciones.Suma cannot be cast to class operaciones.PluginOperacion (operaciones.Suma is in unnamed module of loader logica.PluginClassLoader @daf4db4; operaciones.PluginOperacion is in unnamed module of loader java.net.URLClassLoader @6576fe71)
        at logica.OperacionesManager.loadOperaciones(OperacionesManager.java:75)
        at gui.commands.RefreshCommand.execute(RefreshCommand.java:28)
        at gui.GUI_CalcSimple.<init>(GUI_CalcSimple.java:124)
        at gui.GUI_CalcSimple$1.run(GUI_CalcSimple.java:60)

我一直在做一些研究,發現問題是因為接口是由與我的自定義類加載器不同的類加載器加載的,但我不知道如何解決這個問題。

謝謝

NB: Because java represents all type kinds (classes, interfaces, enums, etc) java.lang.Class I'll use the term 'class' for the rest of this answer, but that encompasses interfaces and enums and such as well.

通常,我們 java 開發人員說,一個類型(類、接口、枚舉等)只被系統加載一次(例如,只有一個java.lang.Class實例)

這是真實的。

但是,“一個類”,該定義需要一些捏造:class不僅由其完全限定名稱( operaciones.Suma )定義。 它實際上是由它的名稱和它的加載器的組合定義的。

現在,在正常情況下,給定虛擬機中只有一個相關的加載器:它加載了具有您的 main 方法的 class 並將加載執行此方法最終需要的所有內容,並且它查看類路徑來完成它的工作.

重要:實例的兼容性

If you somehow end up with 2 separate loaded classes both named, say, java.lang.Integer , by using 2 class loaders that each individually loaded that class, then these types are completely incompatible. 您會遇到瘋狂的錯誤,例如“java.lang.Integer 類型的實例不能分配給 java.lang.Integer 類型的變量”。

重要:邊界類型的概念

在您的模塊系統中,有 3 個世界。 在您的主應用程序中,您可以在內部執行這些操作。 該插件甚至不知道這一切。 它的東西標記為私人等。

在插件系統中,它也有私有組件。

但是,還有第三個世界:兩者之間是什么。 據推測,您最終會在主應用程序代碼中生成一個字符串,然后將其交給插件。 這意味着java.lang.String是邊界類型。 該插件將operaciones.PluginOperacion作為它需要的東西(畢竟它實現了它)。 但是您的主要代碼也是如此。 是邊界類型。

關鍵點:所有邊界類型都必須由一個 class 加載程序(插件代碼和主應用程序)加載,否則您不能將它們用作邊界類型。

因此,您所觀察到的解釋很簡單:插件最終在其自己的加載器中加載了邊界類型,而不是在您的主 class 的加載器中,這就是您需要修復的。

重要提示:“什么是裝載機?”

“加載這個類的加載器”是什么意思? 很簡單:ClassLoader 最終調用自己的本地defineClass方法,傳遞字節碼的字節數組。 這使得您在“加載器”上調用defineClass方法的defineClass 每當 class 最終需要另一個 class 來完成它的工作時,它會立即要求它的 class 加載器這樣做。 即使對於像java.lang.String這樣簡單的東西也是如此。

重要:ClassLoader 有父級

ClassLoader API 設計得相當靈活,但其最基本的用途如下:

  1. 類加載器有父加載器。
  2. 要加載任何資源,CL 將首先要求其父級加載它 他們調用defineClass ,使其加載的 class 設置為您的父級是加載器,而不是您的自定義加載器。
  3. 只有當父母無法完成時,CL才會自己完成。 你最終調用了defineClass ,你現在是加載器。 您從 #1 加載 go 所需的任何類型,並且將再次導致“先詢問父母,只有當它不能時,我們才加載它”。

您不必這樣做,您可以選擇不問您的父母並始終自行加載。 有時這樣做是有原因的。

正確的設計

所以,訣竅是,使用父系系統來確保邊界類型只加載一次。 可能是由您為主應用程序設置的一個類加載器,但這可能是過度設計它:您的主應用程序和所有邊界類應由 java 標准加載器(使用您的 main 方法加載 class)和插件本身加載它定義的任何類型都由插件的加載器加載。

由於父級,這解決了:插件的加載器有一個父級(主加載器)。 您要求自定義加載程序加載插件。 它首先詢問其父級(主加載器),但由於此插件不在您的類路徑中,所以找不到它。 因此,pluginloader 加載它。 在 pluginloader 完成此操作的那一刻,pluginloader 立即被要求加載operaciones.PluginOperacion ,因為您正在加載的插件 class 擴展/實現了它。

按照 API 的標准意圖,您的 pluginloader 將要求其父級加載它,並且...應該成功,因此您的 pluginloader 返回的jlClass實例仍然由 mainloader 加載。 如,調用getClassLoader()將返回相同的東西YourMainApp.class.getClassLoader()返回。

偉大的? 如何?

class PluginLoader extends ClassLoader {
    public PluginLoader(ClassLoader parent) {
        super(parent);
    }

    public Class<?> findClass(String name) {
        String tgt = name.replace(".", "/") + ".class";
        byte[] bytecode = readFullyFromPluginjar(tgt);
        return defineClass(name, bytecode, 0, bytecode.length);
    }
}

這就是你所要做的。 很簡單,一旦你了解它是如何工作的。

但是,請注意 java 本身將調用loadClass而不是findClass 幸運的是,你繼承的loadClass的 impl 會首先詢問 parent,只有當 parent 找不到時,才會調用findClass (最終會運行上面覆蓋的代碼)。 因此,如果您想編寫一個不符合首先詢問父母的標准意圖的類加載器,您可以重寫 loadClass。 但是,標准意圖通常是您想要的,因此通常覆蓋 findClass,而不是 loadClass。

要使用:

class Main {
    public PluginOperaciones loadPlugin(Path jarLocation, String className) {
        PluginLoader loader = new PluginLoader(Main.class.getClassLoader());
        loader.setJarSearchSpace(jarLocation);
        Class<?> pl = loader.loadClass(className); // load, not find!!
        return (PluginOperaciones) pl.getConstructor().newInstance();
    }
}

祝項目rest好運!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM