2014-10-02 14 views
5

Sommario:Caricamento di un barattolo da un programma Java in esecuzione causa un NoClassDefFoundError causato da un ClassNotFoundException causato da dipendenze inter-classe (ad esempio import dichiarazioni). Come posso aggirarlo?Caricamento di un vaso in fase di esecuzione causa una NoClassDefFoundError/ClassNotFoundException

Il problema in modo più dettagliato:

Sto tentando di caricare codice un file jar - chiamiamolo "Server" - nella Java Virtual Machine attraverso il mio programma Java - Chiamiamo "ServerAPI" - e usa l'estensione e altri trucchi per modificare il comportamento e interagire con Server. ServerAPI dipende dal server, ma se il server non è presente, ServerAPI deve essere ancora in grado di eseguire e scaricare server da un sito Web.

Per evitare errori causati dal caricamento di ServerAPI senza soddisfare le sue dipendenze dal server, ho creato un launcher - chiamiamolo "Launcher" - destinato a scaricare Server e configurare ServerAPI come necessario, quindi caricare Server e ServerAPI, quindi eseguire ServerAPI.

Tuttavia, quando provo a caricare i file jar da Launcher, ottengo degli errori causati dal fatto che i ClassLoaders non sono in grado di risolvere le altre classi nel file da cui dipende la classe da cui viene caricato. In breve, se provo a caricare la classe A, verrà generato un errore se A importa B perché non ho ancora caricato B. Tuttavia, se B importa anche A, sono bloccato perché non riesco a capire come caricare due classi contemporaneamente o come caricare una classe senza che JVM esegua la convalida.

Perché tutte le restrizioni mi hanno portato a questo problema:

Sto tentando di modificare e aggiungere al comportamento dei server, ma per ragioni legali complicate, non riesco a modificare direttamente il programma, quindi ho creato ServerAPI che dipende e può modificare il comportamento del Server dall'esterno.

Tuttavia, per motivi legali più complessi, Server e ServerAPI non possono essere semplicemente scaricati insieme. Launcher (vedi sopra) deve essere scaricato con ServerAPI, quindi Launcher deve scaricare Server. Infine, ServerAPI può essere eseguito utilizzando Server come dipendenza. Ecco perché questo problema è così complesso.

Questo problema si applicherà anche a una parte successiva del progetto, che coinvolgerà un'interfaccia API basata su plugin che deve essere in grado di caricare e scaricare plug-in dai file jar durante l'esecuzione.

ricerca che ho già fatto su questo problema:

Ho letto attraverso e non è riuscito ad essere aiutato da:

  • this question, che affronta solo la questione di un unico metodo e non lo fa affrontare gli errori di dipendenza inter-classe;
  • this question, che non funzionerà perché non riesco a spegnere e riavviare il programma ogni volta che un jar viene caricato o scaricato (principalmente per la parte plugin che ho brevemente menzionato);
  • this question, che funziona solo per le situazioni in cui le dipendenze sono presenti all'avvio del programma;
  • this question, che ha lo stesso problema di # 2;
  • this question, che ha lo stesso problema di # 3;
  • this article, da cui ho imparato a conoscere il loadClass nascosta (String, boolean) metodo, ma cercando con veri e falsi valori non ha aiutato;
  • this question, che ha lo stesso problema del numero 1;

e altro. Niente ha funzionato.

// EDIT: tentativi ho fatto finora:

Ho provato con URLClassLoaders per caricare il vaso utilizzando il JarEntries dal JarFile simile a this question. Ho provato questo utilizzando e chiamando un metodo loadClass(String)URLClassLoader e facendo una classe che si estende URLClassLoader in modo che io possa utilizzare loadClass(String, boolean resolve) per provare a forzare il ClassLoader per risolvere tutte le classi che carica. Entrambi i modi, ho ottenuto questo stesso errore:

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 

// END EDIT

// EDIT 2:

Ecco un esempio del codice che ho usato per caricare un classe durante il tentativo di risolverlo. Questo era all'interno di una classe che ho realizzato che estende URLClassLoader. Sulla riga che inizia con Class<?> clazz = loadClass(, ho provato ad usare true e false come argomento booleano; entrambi i tentativi hanno portato allo stesso errore sopra.

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

// END EDIT 2

mi rendo conto che ci deve essere una semplice soluzione a questo, ma per la vita di me non riesco a trovarlo. Qualsiasi aiuto mi renderebbe eternamente grato. Grazie.

+0

Hai confermato dallo stack trace che è in effetti l'importazione stessa a causare il problema, e non (diciamo) un accesso involontario dell'altra classe da un costruttore o da una variabile statica? (E se è davvero l'importazione, cosa succede se rimuovi l'importazione e completi tutti i riferimenti all'oggetto alla classe?) Infine, puoi pubblicare come stai caricando il barattolo? –

+0

In casi come questo, può essere utile prendere a modello un esempio molto semplice con due classi do-nothing in jar separati e capire se hanno lo stesso problema quando li si carica. In caso contrario, continua ad aumentare la complessità del modello fino a quando non rispecchia i modelli utilizzati nella tua app di vita reale. Ad esempio, ho notato (in un ambiente OSGi) che al classloader piace risolvere tutte le classi a cui si fa riferimento in un modo o nell'altro da una classe che carico direttamente, ma non tenta di risolvere le classi a cui si fa riferimento in quella -step-rimosso classi fino a quando non vengono invocati. –

+0

(In tal caso, l'aggiunta di una classe shim tra le classi di origine e di destinazione è stata sufficiente per rendere felice il classloader, purché la classe shim non venga mai richiamata fino a quando non viene caricata la classe di destinazione.) –

risposta

2

In modo imbarazzante, ho trovato che la risposta era che il messaggio di errore stava dicendo la verità.javax.persistence.AttributeConverter, la classe che il caricatore stava richiedendo non era presente, non era nel barattolo.

Ho risolto il problema caricando solo la classe principale e le classi di riferimenti ClassLoader, essenzialmente caricando tutte le classi nel jar utilizzate nel programma, che è tutto ciò di cui ho bisogno.

Ora, potrei giurare che ho controllato per questo prima e ho trovato quella classe; Immagino di aver effettivamente controllato il repository open source Apache per la classe piuttosto che il server attuale quando l'ho verificato. Non riesco a ricordare.

In ogni caso, manca AttributeConverter. Non so come o perché siano riusciti a compilare un jar con dipendenze mancanti, ma immagino che i loro processi principali non usino mai quella parte del codice, quindi non ha mai gettato errori.

Mi dispiace aver sprecato il tempo di tutti ... compreso il mio. Sono stato bloccato su questo problema per un po 'di tempo.

morale di questa storia:

Se stai cercando di caricare un jar eseguibile, non si preoccupano di caricare tutte le classi in un barattolo a meno che effettivamente necessario. Basta caricare la classe principale; questo caricherà tutto ciò che il programma deve eseguire.

// EDIT:

Ora ho iniziato ad avere lo stesso errore, ma non appare fino tento di chiamare un metodo da una classe caricata. La domanda è apparentemente ancora aperta. Si prega di downvotare e ignorare questa risposta.