Newsgroup: it.comp.java
Titolo: Loading dinamico dei jar senza usare classpath

Salve a tutti.
Ho un'applicazione che deve utilizzare delle classi presenti in un file 
.jar e fin qui niente di difficile. Il problema è che non posso 
specificare il .jar nel CLASSPATH poichè il file .jar stesso viene 
identificato a runtime dall'utente, tramite una finestra di dialogo 
nella quale viene indicato il percorso del file system locale dove è 
memorizzato il jar.
Nella classe della mia applicazione che si occupa di utilizzare le 
classi del jar ho provato a fare il loading del jar con URLClassLoader, 
utilizzando questo codice:
File f = new File("ilMioFileJar.jar");
URL[] url = new URL[]{f.toURL()};
URLClassLoader mioCL = new URLClassLoader(url);
ma non ottengo il risultato voluto. Alcune classi non vengono trovate, 
dando una java.lang.ClassNoDefFoundError a runtime.
Cosa strana è che però l'URLClassLoader identifica le classi che vengono 
indicate come non trovate in java.lang.ClassNoDefFoundError; infatti con 
questo codice
//piglio la classe non trovata
Class c = mioCL.loadClass("ClasseNonTrovata");
//prendo un'istanza della classe
TipoDellaClasse tdc = (TipoDellaClasse)c.newInstance()
non ottengo errori di alcun tipo.
La struttura del file jar è:
mioJar.jar
    |
    |---- pack1
            |
            |----sottopack1
                     |
                     |---- customException.class
e la java.lang.ClassNoDefFoundError riguarda la classe CustomException.
Come posso fare per fare il "loading completo" del file jar, oppure fare 
in modo che le classi vengano in qualche modo trovate?
Avevo provato anche a modificare la 
System.setProperty("java.class.path", "path comprensivo del jar che mi 
serve") ma non funziona.
Grazie a tutti quelli che mi aiuteranno.
Ciao!

 non ottengo errori di alcun tipo.
per errori intendi errori di compilazione?
usi il nome completo della classe?
(occhio che Java è case sensitive)
pack1.sottopack1.customException
ciao
Andrea

 Andrea
Ciao Andrea, grazie per la risposta.
In fase di compilazione non ottengo errori perchè inserisco i jar nel 
mio ambiente di programmazione (NetBeans).
Gli errori avvengono a runtime: faccio girare la mia applicazione non da 
Netbeans ma da un semplice file batch; ho usato i nomi qualificati, 
ossia indicando il percorso dei package all'interno dei Jar e stando 
anche attento a maiuscolo/minuscolo.
Il fatto è che io non istanzio customException, ma un'altra classe, che 
mettiamo sia pack1.MainClass. Questa MainClass farà le sue cose e ad un 
certo punto, penso, si serva della definizione di 
pack1.sottopack1.customException senza riuscire a trovarla: a questo 
punto viene sollevata la NoClasseDefFoundError.
Ho provato a mettere i file jar nel classpath (semplicemente usando nel 
file batch il comando SET CLASSPATH=MioJar1.jar;MioJar2.jar eccetera) e 
tutto funziona a meraviglia. Non sono quindi i jar o le classi in esse 
contenuti a non funzionare, ma proprio il meccanismo di loading dinamico 
con URLClassLoader...
Grazie :-)
Ciao,
Davide

 wrote in message 
news:MTund.42702$Es2.9...@twister2.libero.it...

Se scrivi File  f = new File(".") e gli chiedi f.getAbsolutePath() vedrai 
che è differente a seconda da dove lanci il programma.
Io disolito con netbeans gli do un parametro di ingresso, e poi x quando 
lancio il jar metto un if nel main
Spero ti possa aiutare
Ciao

 con URLClassLoader...
ho usato varie vole URLClassLoader e non ho mai avuto problemi
dovresti spiegarmi cosa intendi quando dici :
<infatti con questo codice
<//piglio la classe non trovata
<Class c = mioCL.loadClass("ClasseNonTrovata");
<//prendo un'istanza della classe
<TipoDellaClasse tdc = (TipoDellaClasse)c.newInstance()
<non ottengo errori di alcun tipo.
questo codice riesci a compilarlo ma a runtime ti da errore?
per TipoDellaClasse intendi un'istanza di ClasseNonTrovata?
se sě allora ho una mezza idea di quale sia l'inghippo ;)
comunque sarebbe tutto piů semplice se riportassi
il codice esattamente come lo stai eseguendo
ciao
Andrea

 il codice esattamente come lo stai eseguendo
Ciao Andrea.
Hai perfettamente ragione, dare un aiuto su qualcosa che non si ha 
davanti č difficile, se non impossibile :)
Sto cercando di usare qizxopen.jar e questo č il codice con cui lo uso. 
(se per capire meglio serve il jar, lo posso uppare su un mio spazio web).
//INIZIO CODICE
/*
   1)creo il classloader con il percorso del mio jar
   2)verifico che venga svolta correttamente la loadClass della
     classe che poi mi da ClassNoDefFoundError
   3)uso le classi del jar
*/
try{
     //prendo il file, creo l'url e il classloader
     File fileJar = new File("C:\\librerie\\Qizx\\qizxopen.jar");
     java.net.URL url = fileJar.toURL();
     java.net.URL[] urls = new java.net.URL[]{url};
     java.net.URLClassLoader cl = new java.net.URLClassLoader(urls);
     //carico la classe che mi genera l'errore in una fase successiva
     try{
         Class c = cl.loadClass("net.xfra.qizxopen.xquery.XQueryException");
         net.xfra.qizxopen.xquery.XQueryException obj = 
(net.xfra.qizxopen.xquery.XQueryException) c.newInstance();
         //a runtime la println sotto viene eseguita, la classe č quindi 
caricata
         System.out.println("Classe caricata.");
     } catch (Exception ex){
       //questo a runtime non viene stampato, non c'č eccezione
       System.out.println("Classe non caricata\n" + ex.toString());
     }
     //adesso provo a usare il jar: istanzio la classe "principale", che
     //č quella che mi permette di usare le funzionalitŕ del jar:
     //a runtime č qui che ho l'errore!
     try{
         net.xfra.qizxopen.xquery.XQueryProcessor proc = new 
net.xfra.qizxopen.xquery.XQueryProcessor("", "");
     } catch (Throwable ex) {
        //qui mi viene sollevata la ClassNoDefFoundError relativa a 
XQueryException
        System.out.println("Errore in Qizx.\n" + ex.toString());
     }
} catch (Exception e) {
    System.out.println("Errore in loader.\n" + e.toString());
}
//FINE CODICE
Qui ho messo il nome completo (con i package) delle classi del jar, per 
identificare meglio quali sono le classi in questione, mentre nel mio 
codice ho fatto un semplice import di net.xfra.qizxopen.xquery.*;
Al posto di usare File file = new File("Il mio jar.jar") e poi costruire 
l'URL dal File, ho anche provato a scrivere cosě:
java.net.URL url = new 
java.net.URL("jar:file:/c/librerie/Qizx/qizxopen.jar!/");
ma il risultato non cambia.
Ho anche provato a unrarrare il jar e ho controllato che effettivamente 
ci siano i .class corrispondenti sia a XQueryProcessor (ce ne sono due: 
XQueryProcessor.class e XQueryProcessor$1.class) che a XQueryException.
Anche se magari non si deve fare, decompilando XQueryProcessor.class ho 
visto che in alcuni metodi č presente l'uso di XQueryException.
A compile-time non ho errori, perchč in NetBeans ho incluso il jar nel 
FileSystem del mio progetto, mentre a runtime non ho il jar né nella 
variabile d'ambiente CLASSPATH né nell'istruzione SET CLASSPATH del file 
batch con cui faccio partire la mia applicazione e viene lanciata la 
ClassNoDefFoundError.
Certo, sarebbe molto piů comodo specificare in modo statico dove trovare 
il jar ma purtroppo (o per fortuna, cosě imparo qualcosa di nuovo!) mi 
serve questa ricerca a runtime.
Se mi sapessi dare un consiglio ti sarei veramente molto grato :)
Ciao,
Davide

 serve questa ricerca a runtime.
Un chiarimento: per adesso il nome del jar è inserito nel codice per mia 
comodità di testing di questa funzionalità, mentre poi verrà preso da un 
input fornito dall'utente :-)
Ciao,
Davide


il problema è questo :
stai usando correttamente l'URLClassLoader
ma dimentichi che il codice che hai riportato
è stato caricato con un classloader differente
ogni classe ha un classloader e tale classloader
viene usato per caricare ciò che serve alla classe
durante l'esecuzione
(potresti avere classloader diversi per ogni classe)
se tu crei un nuovo classloader che include
un determinato package allora tale package
sarà accessibile solo alle classi che
carichi con tale classloader
(i classloader hanno una gerarchia ad albero)
quindi non puoi accedere alla classe come alla riga :
net.xfra.qizxopen.xquery.XQueryException obj =
(net.xfra.qizxopen.xquery.XQueryException) c.newInstance();
(che mi sembra molto strano non ti dia errore)
oppure alla riga :
 > net.xfra.qizxopen.xquery.XQueryProcessor proc = new
 > net.xfra.qizxopen.xquery.XQueryProcessor("", "");
perchè questo è possibile solo se la classe è già nel classpath
come venirene a capo ?
1) usi la reflection
2) crei il tuo classloader e con esso carichi la classe
che contiene la logica della tua applicazione che deve
usare il package che aggiungi dinamicamente
spero di essermi spiegato
ciao
Andrea

 Andrea
e se usasse una interfaccia raggiungibile dal CL di sistema e 
l'implementazione presa dall'URLClassLoader? (ovviamente la classe 
caricata dall'URLClassLoader dovrà implementare tale interfaccia... se 
possibile)
Mauro

 possibile)
anche questo è giusto
(io ad esempio ho fatto così per implementare dei plugin)
ciao
Andrea
http://jame.sf.net

 Mauro
Ciao Mauro, grazie anche a te.
Ho letto i consigli di Andrea e il tuo e ho provato a buttar giù un'idea.
Potrei quindi fare una cosa come quella qui sotto?
Struttura dell'applicazione (la riporto anche sotto per comodità):
app
  |
  |---- gestori
  |         |
  |         |---- QizxInterface
  |         |---- QizxGestore (implements QizxInterface)
  |
  |---- GestoreGenerale
Ed ecco i punti salienti delle varie classi:
//interfaccia caricata dal CL di sistema
public interface QizxInterface{
     public String esegui(String azione);
}
//clase caricata con il mio URLClassLoader, che carica anche il jar
public class QizxGestore implements QizxInteface{
     private XQueryProcessor proc = null;
     public qizxGestore(){
          //prendo la classe contenuta nel jar caricato dinamicamente
         proc = new XQueryProcessor();
     }
     public String esegui(){
         //fa qualcosa con la variabile proc
         String result = proc.doSomething();
         return result;
     }
}
//classe caricata con il CL di sistema
public class gestoreGenerale(){
     //carico nel mio CL sia il jar che la classe QizxGestore che 
implementa QizxInterface
     File fileJar = new File("C:\\librerie\\Qizx\\qizxopen.jar");
     //percorso dove trovare il file app.gestori.QizxGestore.class
     File file = new File("C:\\Davide\\");
     java.net.URL u1 = fileJar.toURL();
     java.net.URL u2 = file.toURL();
     java.net.URL[] urls = new java.net.URL[]{u1, u2};
     java.net.URLClassLoader cl = new java.net.URLClassLoader(urls);
     Class qizxClass = cl.loadClass("app.gestori.QizxGestore");
     QizxInterface qizx = (QizxInteface) qizxClass.newInstance();
     qizx.esegui("save"); //uno dei comandi possibili
}
La struttura della mia applicazione è quindi così:
app
  |
  |---- gestori
  |         |
  |         |---- QizxInterface
  |         |---- QizxGestore (implements QizxInterface)
  |
  |---- GestoreGenerale
Quindi ho il classLoader di sistema che carica GestoreGenerale (che è 
anche il main della mia applicazione) e QizxInterface, mentre il cl che 
costruisco con URLClassLoader carica QizxGestore e il jar.
Domanda: ma il classLoader di sistema non carica tutta la gerarchia di 
package che sta sotto app, e quindi anche QizxGestore che io poi voglio 
caricare con un altro classLoader per farle vedere il jar? Può essere 
che quindi il classLoader che costruisco con URLClassLoader non mi 
carichi QizxGestore perchè è già stato caricato dal cl di sistema, 
facendo quindi saltare la "visione reciproca" tra QizxGestore e il jar?
Quando poi chiamo qizx.esegui("save") posso farlo direttamente così, 
senza usare la reflection, poichè so che qizx è di tipo QizxInterface e 
quindi ha quel metodo... giusto?
Grazie mille, veramente :)
Ciao,
Davide


 >> [cut]
ciao,
il modo di procedere è + o - giusto, ma ti ricordo che stiamo parlando 
di URL e jar, ti consiglio di dare una ripassata a questi argomenti... 
piccolo spunto x iniziare: devi far capire a URLClassLoader che vuoi 
caricare un *jar* in *locale* quindi......
Mauro

 caricare un *jar* in *locale* quindi......
Una ripassata... piuttosto una studiata, visto che non ho mai avuto a 
che fare con file Jar! :)
L'unica cosa che ho trovato per indicare un file Jar č la class JarFile, 
ma da questa non posso ricavare un URL, e JarURLConnection: questa 
sembra fare al caso mio, poichč posso indicare il percorso di un JAR e 
ottenerne l'URL tramite il metodo getJARFileURL(). Il fatto č che la 
classe č astratta...
Avevo anche provato a crare il file cosě:
java.net.URL url = new 
java.net.URL("jar:file:/c/librerie/Qizx/qizxopen.jar!/");
che penso ottenga gli stessi risultati dell'usare JarURLConnection, ma 
senza esito positivo.
Mentre per quanto riguarda il fatto che il file č in locale e non in 
remoto č specificato "file" e non "http". Intendevi questo?
Non so, sinceramente sono le uniche cose che ho trovato sul Javadoc 
della Sun e in generale guardando in rete: forse perň non ho trovato le 
giuste fonti; tu sai consigliarmi qualche documento piů dettagliato?
Grazie,
Davide
P.S: il mio obiettivo non č farmi scrivere il codice eh, ben inteso :) 
E' imparare a gestire queste risorse!

 E' imparare a gestire queste risorse!
oggi voglio essere cattivo, quindi ti passo un po' di codice che ho 
provato e funge...
             URL url = new URL("jar:file:E:\\impl\\app.jar!/");
             URLClassLoader cl = new URLClassLoader(new URL[] {url});
             Class tiClass = cl.loadClass("p2.Test1");
             TestInterface ti = (TestInterface) tiClass.newInstance();
             ti.out();
ovviamente la classe Test1 è nel package p2 e implementa TestInterface.
Mauro