PDA

Visualizza versione completa : [Java] Caricare dinamicamente un jar e usare tutte le sue classi


Dav82
19-11-2004, 16.03.02
Ciao a tutti :)

E' da un po' che sto cercando di risolvere questo problema ma non ne vengo a capo :(

Ho un programma Java che ha bisogno di usare delle classi contenute in dei file jar, e fin qui niente di male: basta mettere il file jar nel CLASSPATH e tutto funziona tranquillamente.
Il fatto è che devo caricare il jar a runtime, perchè questo si può trovare in un qualsiasi percorso del file system locale, quindi niente posizionamento fisso nella variabile d'ambiente CLASSPATH.
Ho provato modificando il java.class.path così:

String oldPath = System.getProperty("java.class.path");
String newPath = oldPath + "ilMioFileJar.jar;";
System.setProperty("java.class.path", newPath);


ma niente da fare. Da qualche parte ho anche letto che così non può funzionare perchè si produrrebbe solo una copia modificata del CLASSPATH originale e il classloader se ne frega di questa copia e usa sempre il CLASSPATH originale.

Ho quindi ben pensato di usare la classe URLClassLoader.
Finchè devo caricare una sola classe niente di male, il problema viene propio con i jar. Ecco il codice che ho usato:

File f = new File("ilMioFileJar.jar");
//creo un array contenente l'url del mio file jar
URL[] url = new URL[]{f.toURL()};

//lo do in pasto al classloder
URLClassLoader mioCL = new URLClassLoader(url);

//piglio la classe che mi serve istanziare
Class c = mioCL.loadClass("ClasseCheMiServe");

//prendo un'istanza della classe
TipoDellaClasse tdc = (TipoDellaClasse)c.newInstance()

//e adesso uso la classe!


Tutto bene fino alla fine, ma al momento di usare la classe, la quale usa altre classi presenti nel jar, sorge il problema: sembra che non riesca a fare il loading di altre classi che gli servono e che sono contenute nel jar.
Mi viene restituito un errore del tipo "mi manca la classe pack1.pack2.ClasseX" e allora ho provato anche a fare il loading preventivo di questa classe:


//piglio la classe che mi serve istanziare
Class c = mioCL.loadClass("ClasseCheMiServe");

//piglio la classe che mi viene segnalata nell'errore
Class d = mioCL.loadClass("pack1.pack2.ClasseX");

//prendo un'istanza della classe
TipoDellaClasse tdc = (TipoDellaClasse)c.newInstance()

//e adesso uso la classe!


ma nulla cambia: mi viene restituito sempre lo stesso errore.

Come faccio?
C'è un modo per fare il loading "completo" di un jar, o un modo per fare caricare dinamicamente le classi presenti nel jar in caso di necessità?

Oppure il problema va affrontato con l'uso di altre classi?

Resta sempre il vincolo che il .jar non può essere specificato nella variabile di sistema CLASSPATH, poichè l'utente (bastardo) può decidere di metterlo dove vuole sul proprio disco e me lo indica con una bella finestrella di dialogo durante l'esecuzione del programma.

Se non avete capito qualcosa perchè mi sono espresso male (sicuro!) chiedetemi pure :)

Grazie a tutti :)

(B)

P8257 WebMaster
19-11-2004, 16.47.33
Ciao Dav,
complimenti per le soluzioni che hai cercato ed utilizzato... anch'io spesso ho questo tipo di problemi e non mi è mai piaciuto il sistema di "deploy" di Java (se devo essere proprio sincero...)

Quindi volevo cheiderti:
In che momento l'utente ti specifica questo Jar? .. All'inizio dell'esecuzione del tuo programma? .. In fase di installazione? .. oppure proprio mentre lo sta usando?...

E anche un'altra cosa: E' possibile spezzare il programma in due... ?

Poi ti spiego cosa ho in mente...

Bye :cool:

Dav82
19-11-2004, 17.13.21
Ciao Web, grazie :)

Il programma principale parte, l'utente può, tramite menu, indicarmi il file jar da utilizzare (un programma esterno a cui devo dare in pasto dei dati e che mi fornisce, ovviamente, delle risposte), e io da quel punto in poi devo poter utilizzare il jar indicatomi.

Nel mio programma sono già presenti le classi che gestiscono le classi del jar: in sostanza, per ogni jar che l'utente può caricarmi, ho una classe GestoreJarX che sa come utilizzare il jar; sono presenti quindi tanti piccoli plugin implemenati dalle classi GestoreJarX che possono essere utilizzati una volta indicato il jar da utilizzare per il plugin specifico (io ovviamente in fase di compilazione ho tutti i jar impostati nelle variabili d'ambiente di NetBeans, quindi non ho problemi di sorta).

Una volta che l'utente mi indica il percorso dove trovare il jar, me lo salvo in un file di configurazione e al successivo riavvio dell'applicazione uso sempre lo stesso percorso. L'utente può cambiare anche il percorso di un jar per uno dei plugin, e io devo adattarmi al cambiamento.

Che cosa intendi per "spezzare il programma in due"?
Perchè così è, volendo, già spezzettato: c'è il programma principale con le sue funzionalità, il gestore di tutti i plugin GestorePlugin che si integra nel programma principale, e i vari plugin GestoreJarX che si "registrano" presso il GestorePlugin.

Io posso intervenire da GestorePlugin in giù, perchè mi occupo solo di questo.

Grazie :)

Dav82
19-11-2004, 19.06.37
Adesso funziona :eek: :eek: :eek:

Giuro, non ho cambiato niente :confused:


La cosa bella è invece che ho un attributo privato di una classe che viene posto uguale a un certo valore nel costruttore, nella classe non ci sono altre istruzioni che toccano sto attributo privato, eppure a un certo punto me lo ritrovo cambiato!
Beh... tolto assegnamento nel costruttore, messo nella dichiarazione dell'attributo e aggiunto un bel final all'attributo stesso e adesso non cambia più...

Sembra... sembra funzionare!

Adesso provo a farlo girare un po' (leggasi: testing alla carlona :p) con tutti i plugin e vedo come va :)

Web, tieniti pronto che fra un po' sicuramente mi crasha tutto e sono ancora qui! :D

Dav82
19-11-2004, 20.02.17
No scherzavo, non funziona. C'era una bella cartella scompattata in un percorso che avevo nel classpath :S

Cmq, nel mio jar c'è sta roba:


mioJar.jar
|
|---- pack1
|
|----sottopack1
|
|---- customException.class


e mi viene restituito l'errore:

java.lang.NoClassDefFoundError: pack1/sottopack1/customException

Però l'operazione di loading della classe customException con mioCL.loadClass("pack1.sottopack1.customException") va a buon fine, non mi solleva eccezioni: quindi il mioCL riesce a "vedere" che la classe esiste...

P8257 WebMaster
20-11-2004, 00.54.54
Ok bene,
se ho capito bene, l'utente sceglie il jar da utilizzare e tu scrivi la preferenza in un file di configurazione che poi vai ad utilizzare nelle successive sessioni...

Volendo essere sbrigativi e un po' grezzi, potrei suggerirti due metodi:

1 - "spezzi" il programma, ovvero, la parte in cui l'utente decide il jar da utilizzare termina la sua esecuzione una volta che è stata effettuata la scelta eseguendo due operazioni: scrivendo il file di configurazione (come già fa) e creando uno startup (ad esempio un .bat) ad hoc per lanciare l'altra parte del programma con il classpath corretto, infine si preoccupa di lanciare il bat appena creato dando all'utente la sensazione di "continuità" del programma, mentre in realtà il vero e proprio applicativo parte ora...

2 - Dopo la selezione dell'utente prelevi il jar dal path che ti ha indicato e lo estrai in una tua cartella temporanea settata in maniera statica nel tuo classpath oppure caricando le classi dinamicamente come hai già fatto nel tuo esempio; questo ti permette di usare un percorso fisso e di non dover utilizzare il jar ma solo i .class così estratti.
Quando l'utente cambia la preferenza, tu svuoti la cartella temporanea per poterci estrarre dentro un altro jar...

Tutto questo, posto per assodato che io abbia afferrato il tuo ragionamento :D...

Per altre soluzioni più "professionali" ti rimando a domani, dopo una sana dormita... :D

Bye :cool:

Dav82
20-11-2004, 02.32.44
Grazie Web (Y)

Devo pensar bene come applicare le due soluzioni... la prima è forse più problematica... la seconda è forse più semplice.... ma è tardi, una bella dormita fa sicuramente bene anche a me :D

Così riesco anche a spiegarti meglio sta architettura del cavolo! :p

Dav82
20-11-2004, 12.59.34
Mo ti spiego!
Facciamo conto che l'applicazione più grande abbia a disposizione un testo scritto in italiano. Io mi occupo dello strumento "Traduzione", che consta di molti plugin: traduzione in inglese, tedesco, francese, spagnolo, latino ecc ecc. Io ho una classe per ogni traduttore, e ognuna di queste classi sa come utilizzare un determinato jar (di terze parti) per ottenere la traduzione di un testo nella lingua supportata dal jar. L'applicazione viene però distribuita senza i jar, è l'utente che si deve preoccupare di reperirli e indicare al mio strumento "Traduzione" dove beccare i jar corretti.
Mettiamo che l'utente abbia indicato allo strumento dove trovare i jar per Inglese, Tedesco e Francese. In fase di esecuzione, può chiedere la traduzione anche solamente con Tedesco e Francese: tutti i plugin di cui è stato indicato il jar devono però essere attivi per poter essere richiamati.

Quindi potrei fare così: siccome so, in fase di startup, quanti plugin al massimo sono supportati, posso riservarmi di impostare un classpath così:

SET CLASSPATH = %CLASSPATH%; C:\MioProg\Traduzione\plug1.jar; C:\MioProg\Traduzione\plug3.jar; C:\MioProg\Traduzione\plug2.jar; C:\MioProg\Traduzione\plug4.jar;Ecc.Ecc;

e per ogni jar che l'utente mi indica io piglio il jar e lo copio rinominato come plugX.jar e a sto punto lo posso usare.
E' un po' dispendioso perchè devo copiarmi un bel po' di file, e poi il bello è che alcuni plugin sono composti da più jar... e non saprei come fare :confused:

Posso anche seguire un'altra strategia: nel classpath, al posto di riservarmi i jar, mi riservo delle cartelle, una per ogni plugin, in cui copiare e scompattare i jar e da lì usarli, come mi hai detto tu... devo vedere se è una cosa fattibile come dimensioni in MB: tutto il programma occupa 3MB di Jar, e rischio con questa tecnica di usare qualche decina di MB per i Jar dei plugin scompattati...

edit: ho appena provato con uno dei plugin che consta di 19 jar (:eek: ): jarrati occupano 3MB, scompattati sono più di 7MB (per 12MB di spazio su disco col mio FS). Per fortuna così sono solo due su sette: cinque sono invece composti da un unico jar.

riedit: non ho dormito abbastanza! :p Se anche indicavo solo il jar "padre" di tutti di un determinato plugin nel classpath, tutto funzionava a meraviglia, perchè risolveva lui le dipendenze in non so che modo. Quindi mi basta copiare tutti i jar di tutti i plugin nella cartella C:\MioProg\Traduzione e mettere nel CP solo i jar "padri" dei plugin, quindi uno per ogni plugin :) (almeno a occhio e croce)


Dai, adesso (dopo pranzo) provo a vedere quanto tempo ci mette su un pc dalle prestazioni decenti e non come il mio portatile, e vediamo se sono accettabili ;)

Dav82
20-11-2004, 13.22.33
Originariamente inviato da Dav82
Quindi mi basta copiare tutti i jar di tutti i plugin nella cartella C:\MioProg\Traduzione e mettere nel CP solo i jar "padri" dei plugin, quindi uno per ogni plugin :) (almeno a occhio e croce)

Eh no :S
Ci sono due plugin diversi che hanno un jar chiamato con lo stesso nome :crying:

Ok, faccio così: creo tante cartelle C:\MioProg\Traduzione\PluginX, in ognuna ci copio tutti i jar di un plugin e inserisco nel classpath tanti C:\MioProg\Traduzione\PluginX\JarPadrePluginX.jar

E che cavolo!!!

Dav82
06-12-2004, 20.14.17
Webbbbbbbbbbbbb, mi ero scordato che ho risolto! :)

Siccome con i classloader non sono riuscito a cavarci niente, ho usato la reflection e via ;)

Grassie mille (Y) :)