簡體   English   中英

在運行時加載jar會導致NoClassDefFoundError / ClassNotFoundException

[英]Loading a jar at runtime causes a NoClassDefFoundError/ClassNotFoundException

簡介: 從正在運行的Java程序加載jar會導致由類間依賴性(例如import語句)引起的ClassNotFoundException導致的NoClassDefFoundError 我怎么能繞過它呢?

問題更詳細:

我試圖以編程方式加載一個jar文件 - 讓我們稱之為“服務器” - 通過我自己的Java程序進入Java虛擬機 - 讓我們稱之為“ServerAPI” - 並使用擴展和一些其他技巧來修改行為和服務器交互。 ServerAPI依賴於Server,但如果Server不存在,ServerAPI仍然必須能夠從網站運行和下載Server。

為了避免因ServerAPI加載而導致的錯誤而不滿足Server的依賴性,我制作了一個啟動器 - 讓我們稱之為“Launcher” - 這是為了下載Server並根據需要設置ServerAPI,然后加載Server和ServerAPI,然后運行ServerAPI。

但是,當我嘗試從Launcher加載jar時,我會收到錯誤,因為ClassLoaders無法解析文件中其加載的類所依賴的其他類。 總之,如果我嘗試加載類A ,它會拋出,如果一個錯誤A進口B ,因為我沒裝B呢。 但是,如果B也導入A ,我就會陷入困境,因為我無法弄清楚如何一次加載兩個類或如何在沒有JVM運行其驗證的情況下加載類。

為什么所有的限制都讓我遇到了這個問題:

我試圖修改並添加到Server的行為,但由於復雜的法律原因,我無法直接修改程序,所以我創建了依賴的ServerAPI,可以從外部調整Server的行為。

但是,出於更復雜的法律原因,Server和ServerAPI不能簡單地一起下載。 Launcher(見上文)必須與ServerAPI一起下載,然后Launcher需要下載Server。 最后,可以使用Server作為依賴項運行ServerAPI。 這就是為什么這個問題如此復雜的原因。

此問題也適用於項目的后續部分,該部分將涉及基於插件的API接口,該接口需要能夠在運行時從jar文件加載和卸載插件。

關於這個問題的研究我已經做過:

我已經閱讀並且未能得到以下方面的幫助:

  • 這個問題 ,只解決了單個方法的問題,並沒有解決類間依賴性錯誤;
  • 這個問題 ,這是行不通的,因為每次加載或卸載jar時我都無法關閉並重啟程序(主要是我剛才提到的插件部分);
  • 這個問題 ,只適用於程序啟動時存在依賴關系的情況;
  • 這個問題與#2有同樣的問題;
  • 這個問題與#3有同樣的問題;
  • 這篇文章 ,我從中了解了隱藏的loadClass(String,boolean)方法,但嘗試使用truefalse值並沒有幫助;
  • 這個問題與#1有同樣的問題;

和更多。 沒有任何效果。

//編輯: 我到目前為止所做的嘗試:

我嘗試使用URLClassLoaders使用JarFileJarEntries加載jar,類似於這個問題 我通過使用和調用URLClassLoaderloadClass(String)方法並通過創建擴展URLClassLoader的類來嘗試這一點,以便我可以利用loadClass(String, boolean resolve)來嘗試強制ClassLoader解析它加載的所有類。 兩種方式,我都有同樣的錯誤:

I couldn't find the class in the JarEntry!
entry name="org/apache/logging/log4j/core/appender/db/jpa/converter/ContextMapAttributeConverter.class"
class name="org.apache.logging.log4j.core.appender.db.jpa.converter.ContextMapAttributeConverter"
java.lang.NoClassDefFoundError: javax/persistence/AttributeConverter
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at Corundum.launcher.CorundumClassLoader.load(CorundumClassLoader.java:52)
    at Corundum.launcher.CorundumLauncher.main(CorundumLauncher.java:47)
Caused by: java.lang.ClassNotFoundException: javax.persistence.AttributeConverter
    at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 12 more

//結束編輯

//編輯2:

下面是我在嘗試解析類時加載類的代碼示例。 這是我創建的擴展URLClassLoader 在以Class<?> clazz = loadClass(開頭的行上Class<?> clazz = loadClass(我嘗試使用true和false作為布爾參數;兩次嘗試都導致上面的錯誤相同。

public boolean load(ClassLoadAction class_action, FinishLoadAction end_action) {
    // establish the jar associated with this ClassLoader as a JarFile
    JarFile jar;
    try {
        jar = new JarFile(jar_path);
    } catch (IOException exception) {
        System.out.println("There was a problem loading the " + jar_path + "!");
        exception.printStackTrace();
        return false;
    }

    // load each class in the JarFile through its JarEntries
    Enumeration<JarEntry> entries = jar.entries();

    if (entries.hasMoreElements())
        for (JarEntry entry = entries.nextElement(); entries.hasMoreElements(); entry = entries.nextElement())
            if (!entry.isDirectory() && entry.getName().endsWith(".class"))
                try {
                    /* this "true" in the line below is the whole reason this class is necessary; it makes the URLClassLoader this class extends "resolve" the class,
                     * meaning it also loads all the classes this class refers to */
                    Class<?> clazz = loadClass(entry.getName().substring(0, entry.getName().length() - 6).replaceAll("/", "."), true);
                    class_action.onClassLoad(this, jar, clazz, end_action);
                } catch (ClassNotFoundException | NoClassDefFoundError exception) {
                    try {
                        close();
                    } catch (IOException exception2) {
                        System.out.println("There was a problem closing the URLClassLoader after the following " + exception2.getClass().getSimpleName() + "!");
                        exception.printStackTrace();
                    }
                    try {
                        jar.close();
                    } catch (IOException exception2) {
                        System.out.println("There was a problem closing the JarFile after the following ClassNotFoundException!");
                        exception.printStackTrace();
                    }
                    System.out.println("I couldn't find the class in the JarEntry!\nentry name=\"" + entry.getName() + "\"\nclass name=\""
                            + entry.getName().substring(0, entry.getName().length() - 6).replaceAll("/", ".") + "\"");
                    exception.printStackTrace();
                    return false;
                }

    // once all the classes are loaded, close the ClassLoader and run the plugin's main class(es) load() method(s)
    try {
        jar.close();
    } catch (IOException exception) {
        System.out.println("I couldn't close the URLClassLoader used to load this jar file!\njar file=\"" + jar.getName() + "\"");
        exception.printStackTrace();
        return false;
    }

    end_action.onFinishLoad(this, null, class_action);
    System.out.println("loaded " + jar_path);
    // TODO TEST
    try {
        close();
    } catch (IOException exception) {
        System.out.println("I couldn't close the URLClassLoader used to load this jar file!\njar file=\"" + jar_path + "\"");
        exception.printStackTrace();
        return false;
    }
    return true;
}

//結束編輯2

我意識到必須有一個簡單的解決方案,但對於我的生活,我似乎無法找到它。 任何幫助都會讓我永遠感激不盡。 謝謝。

令人尷尬的是,我發現答案是錯誤信息說實話。 javax.persistence.AttributeConverter ,加載器聲明的類不存在,不在jar中。

我通過僅加載主類和ClassLoader所有引用類來解決問題,實質上是加載程序中使用的jar中的所有類,這就是我所需要的。

現在,我可以發誓我之前檢查過這個並發現了這個課程; 我想我在檢查時必須實際檢查過該類的Apache開源存儲庫而不是實際的服務器。 我不記得了。

在任何情況下,都缺少AttributeConverter 我不知道他們是如何或為什么設法編譯一個缺少依賴項的jar,但我猜他們的主要進程從不使用那部分代碼,因此它從不拋出錯誤。

我很遺憾浪費了每個人的時間......包括我自己的。 我一直堅持這個問題一段時間了。

這個故事的道德:

如果您正在嘗試加載可執行jar,請不要在jar中加載所有類,除非您確實需要。 只需加載主類; 這將加載程序運行所需的一切。

//編輯:

我現在已經開始遇到相同的錯誤,但是直到我嘗試從加載的類中調用方法時它才出現。 問題顯然仍然存在。 請低估並忽略這個答案。

暫無
暫無

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

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