PDA

View Full Version : [JAVA] Caricare jar dinamicamente/a runtime


tylerdurden83
05-02-2010, 00:41
Ragazzi ho bisogno del vostro aiuto, le ho provate tutte, ma mi continua sempre a dare noClassDefFoundError...

Ho provato con classloader custom che estendono ClassLoader oppure URLClassloader, ad aggiungere le url dei jar al Classloader corrente, etc etc, non so più dove :muro:

Grazie mille a chiunque decida di aiutarmi,
Rob

fero86
05-02-2010, 12:19
se il deploy lo fai con JNLP basta che imposti a "lazy" l'attributo download del tag jar.
http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html#resources

PGI-Bis
05-02-2010, 13:45
Cosa intendi per "caricare dinamicamente un jar"? Lo chiedo perchè i jar non sono soggetti ad alcun caricamento mentre ciò che i jar contengono è sempre caricato dinamicamente.

Se usi URLClassLoader tieni conto che il suo comportamento cambia a seconda che l'url di base termini o no con uno slash /

L'eccezione che incontri riporta per caso un'indicazione "wrong name:" seguita da un nome di tipo?

tylerdurden83
05-02-2010, 15:05
import java.net.URL;
import java.io.*;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.util.jar.*;

public class JarFileLoader extends URLClassLoader {

public JarFileLoader (URL[] urls){
super(urls);
}

public void addFile (String path) throws MalformedURLException{
String urlPath = "jar:file://" + path + "!/";
URL url = new URL (urlPath);
File file = new File(path);
addURL (file.toURI().toURL());
}

public void loadClasses(String jarName) throws ClassNotFoundException{
JarInputStream jis = null;
JarEntry entry = null;
try {
jis = new JarInputStream(new FileInputStream(jarName));
entry = jis.getNextJarEntry();
while (entry != null) {
String name = entry.getName();
if (name.endsWith(".class")) {
name = name.substring(0, name.length() - 6);
name = name.replace('/', '.');
System.out.print("> " + name);
try {
loadClass(name);
System.out.println("\t- loaded");
} catch (Throwable e) {
}
}
entry = jis.getNextJarEntry();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}


Ad esempio una cosa di questo tipo. Nella main class creo la mia istanza di JarFileLoader, chiamo addFile(jarURL) per aggiungere il jar al classpath, e poi di nuovo loadClasses(jarName) per caricare le singole classi dentro il jar.
La print
System.out.println("\t- loaded");
viene correttamente stampata per ogni classe, ad es
mio.package.MyClassA loaded
mio.package.MyClassB loaded
etc

Poi nel main:


URL urls [] = {};
JarFileLoader cl = new JarFileLoader (urls);
cl.addFile("E:\\test2\\real_time_monitoring_all.jar");
cl.loadClasses("E:\\test2\\real_time_monitoring_all.jar");


In teoria quindi ho aggiunto il jar al classpath con addFile, e ho caricato i .class con loadClasses.
Tuttavia dopo lancio:

Class.forName("real_time_monitoring.RealTimeMonitoring");

ed ottengo un NoClassDefFoundError su tale classe....

> real_time_monitoring.Event - loaded
> real_time_monitoring.Login$1 - loaded
> real_time_monitoring.Login - loaded
> real_time_monitoring.RealTimeMonitoring - loaded
> real_time_monitoring.Utilities - loaded
> real_time_monitoring.VersionChecker - loaded
> real_time_monitoring.WindowPersistence - loaded
Success!
java.lang.ClassNotFoundException: real_time_monitoring.RealTimeMonitoring
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at newguiupdater.Main.main(Main.java:372)

Credo che mi potrei stare ad incartare anche tra il manifest e java -cp...

PGI-Bis
05-02-2010, 15:41
La classe caricata da un URLClassLoader non è individuata dal forName. Cioè devi scrivere:

Class<?> c = ilJarFileLoader.loadClass(ilNomeDellaClasse)

e non

Class<?> c = Class.forName(ilNomeDellaClasse)

Nel codice che hai incollato ingoi l'eccezione eventualmente rilasciata dal loadClass quindi nulla sai circa l'effettivo esito del tentativo del tuo class loader di trovare ciò che cerchi.

tylerdurden83
05-02-2010, 15:47
La classe caricata da un URLClassLoader non è individuata dal forName. Cioè devi scrivere:

Class<?> c = ilJarFileLoader.loadClass(ilNomeDellaClasse)

e non

Class<?> c = Class.forName(ilNomeDellaClasse)

Nel codice che hai incollato ingoi l'eccezione eventualmente rilasciata dal loadClass quindi nulla sai circa l'effettivo esito del tentativo del tuo class loader di trovare ciò che cerchi.

In realtà ho tolto un po di print, compresa quella del catch throwable, per questioni di leggibilità qui sul forum, nel codice c'è e non dà alcuna eccezione. Anche il main, mi spiego meglio...

cl.loadClasses(System.getProperty("user.dir").concat("\\real_time_monitoring_all.jar"));

loadClasses è void, e quello che fa è chiamare loadClass su ogni .class contenuto nel jar passatogli, quindi effetticamente non uso Class.forName per caricare real_time_monitoring.RealTimeMonitoring, ma loadClass.

PGI-Bis
05-02-2010, 17:28
Se loadClass funziona nel try funziona anche fuori. L'unico errore possibile è che il class loader non sia lo stesso.

tylerdurden83
06-02-2010, 00:33
Se loadClass funziona nel try funziona anche fuori. L'unico errore possibile è che il class loader non sia lo stesso.

Ti chiedo di verificare se ho afferrato bene il concetto, tra l'altro credo di aver ottenuto una soluzione funzionante al problema....

Aspetto numero 1:
- Il bootstrap class loader carica classi di rt.jar, tipo Object
- L' extended class loader è figlio del bootstrap e carica roba che ora non ci interessa
- L'application class loader è figlio dell extended e carica l'applicazione vera e propria. Questo è un'estenzione della classe astratta ClassLoader, generalmente URLClassLoader.
- MyURLClassLoader è figlio dell'application loader.

Ho un jar principale, MyProject.jar, in cui è presente MyURLClassLoader.
Questo jar vuole caricare RealTimeMonitoring.jar, che NON è nel manifest, ne nella clausola java -cp. Il meccanismo di caricamento delle classi è che un class loader demanda al padre il caricamento di una certa classe, ripetitivamente verso l'alto. Se il padre non può caricarla, si riscende verso il basso.

Leggendo qui (http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html) ho capito che la cosa migliore da fare per caricare/ricaricare dinamicamente una classe è questa:

- Definire un'interfaccia, facciamo MyPlugin, senza niente dentro
- Far sì che real_time_monitoring.RealTimeMonitoring implementi quell'interfaccia
- Mettere l'interfaccia in un jar a parte, presente nel classpath, dato che come dice su quel link: To make this work you will of course need to implement your class loader to let the interface or superclass be loaded by its parent.

Ricapitolando, abbiamo:

package real_time_monitoring
import myinterface.MyPlugin
public class RealTimeMonitoring implements MyPlugin{...}

package my_project
import myinterface.MyPlugin
import real_time_monitoring
public class MyProject{...}

uso java -cp "MyInterface.jar" my_project.MyProject

A questo punto però nonostante la classe RealTimeMonitoring è caricata usando loadClass del MyURLClassLoader, se provo a crearne una nuova istanza ottengo comunque quell'error.

Mi pare, devo fare altre prove, che se invece di fare
RealTimeMonitoring rtm = (RealTimeMonitoring)myClass.newInstance();

faccio

MyPlugin rtm = (MyPlugin)myClass.newInstance();

Ti torna? Ho detto fesserie?

PGI-Bis
06-02-2010, 15:01
Il blog non dev'essere recente e, immagino per ragioni di sintesi, è un po' vago.

Non è recente perchè le librerie standard includono URLClassLoader (dalla versione 1.2) all'esatto scopo di permettere il caricamento di classi salvate su file contenuti in percorsi non inclusi nel classpath di un programma java.

La necessità di usare un proprio sottotipo di ClassLoader nasce quando le classi non sono contenute in file (come capita ad esempio se compili un sorgente in memoria o in un database) o quando il codice byte che definisce la classe deve essere manipolato prima del caricamento.

Dunque caricamento di classi contenute in file -> URLClassLoader.

E' vago sulla questione dell'interfaccia. Ci sono due premesse:

1. una classe Java è identificata dal suo nome pienamente qualificato (it.bingo.piripicchio.NomeSemplice) E dal ClassLoader che l'ha caricata. Significa che la classe A caricata dal ClassLoader C1 è diversa dalla classe A caricata dal ClassLoader C2.
2. La richiesta di caricamento nominativa di una certa classe fatta a un certo classloader restituisce sempre la stessa classe finchè questa non sia scaricata dalla jvm. Significa che se io ho una classe A a cui corrisponde un certo codice byte e chiedo ad un class loader C di caricarla una prima volta, ogni ulteriore richiesta di caricamento restituirà sempre la versione di quella classe caricata la prima volta.

Supponiamo di avere questo codice:

it.bingo.Bingo istanza = (it.bingo.Bingo) new MyClassLoader().loadClass("it.bingo.Bingo").newInstance();

Quando viene eseguito il classloader della classe in cui si trova l'enunciato, prima di tutto, carica la classe it.bingo.Bingo (perchè istanza è di questo tipo).

Quando l'URLClassLoader va a caricare una classe con lo stesso nome pienamente qualificato, si danno due casi:

1) MyClassLoader è un delegante rispetto al ClassLoader che ha caricato it.bingo.Bingo per il tipo it.bingo.Bingo: in questo caso la classe restituita è la stessa che è già presente nella JVM ("marchiata" dal ClassLoader che per primo l'ha caricata). Ogni mutamento eventualmente occorso al byte code su file non è considerato - semplicemente perchè il file non è più esaminato.
2) MyClassLoader non è un delegante: in questo caso l'esecuzione fallisce perchè newInstance restituisce un valore di tipo [it.bingo.Bingo, caricato da C1] e la conversione esplicita a cui è sottoposto quel valore è di tipo [it.bingo.Bingo, caricato da C0].

E' chiaro che per poter caricare e aggiornare un certo tipo durante l'esecuzione del programma:

1) Non deve esserci delega rispetto al tipo che si vuole caricare-ricaricare, altrimenti lo carichi ma non lo ri-carichi
2) Non devono esserci tentativi di conversione a tipi ugualmente denominati - perchè pur avendo lo stesso nome la mancanza di delega fa si che i tipi siano diversi.

Nell'esempio di prima, se io dicessi:

Object istanza = new MyClassLoader().loadClass("it.bingo.Bingo").newInstance()

Avremmo un classloader C0 che carica Object, un classloader C1 che carica it.bingo.Bingo ed un assegnamento di un'istanza di it.bingo.Bingo a Object. Funziona? Bingo estende Object. Se l'Object esteso da Bingo è lo stesso Object caricato da C0 allora non ci sono eccezioni dovute all'incompatibilità tra tipi. Permette di ricaricare la definizione di it.bingo.Bingo? Se i delegati di MyClassLoader non hanno accesso alla definizione di it.bingo.Bingo, la classe viene ricaricata ad ogni esecuzione di quell'enunciato.

Nota bene che se anzichè dire new MyClassLoader usassimo una stessa istanza di MyClassLoader per la premessa 1 (un ClassLoader restituisce sempre la stessa classe dopo il primo caricamento finchè la stessa non sia scaricata dalla JVM) il codice non restituirebbe eccezioni ma la classe non verrebbe ricaricata.

Dunque per il caricamento di una classe non inclusa nel classpath del programma, residente su file (locale o remoto) si usa URLClassLoader.
La sostituzione della definizione di un tipo con un altra definizione dello stesso tipo richiede l'uso di un tipo intermedio.

Il tipo intermedio può essere benissimo java.lang.Object. Il problema è che usando Object l'accesso ai metodi specifici della classe che vogliamo poter ricaricare deve avvenire tramite riflessione e facendo ciò si perde il controllo che il compilatore esegue sull'integrità dei tipi: il tentativo di invocare un metodo privato o non esistente è rigettato dal compilatore a meno che quel tentativo non sia riflessivo, caso in cui il compilatore alza le mani e dice "be', io qui nulla posso dire".

Chiudo con l'esempio più breve che si possa fare. Ho una cartella, poniamo "x:\urlclassloaderclasses" in cui c'è una classe Bingo il cui sorgente è:

package it.tukano;

public class Bingo implements Runnable {
private final String TEST_MESSAGE = "hello world";

public void run() {
System.out.println(TEST_MESSAGE);
}
}

compilata in un file Bingo.class in "x:\urlclassloaderclasses\it\tukano\".

Il programma che segue carica per 10 volte la classe it.tukano.Bingo con un intervallo di 10 secondi:

package it.tukano.uc;

import java.net.URL;
import java.net.URLClassLoader;

public class Main {

public static void main(String[] args) throws Throwable {
URL classpath = new URL("file:///x:/urlclassloaderclasses/");
URL[] classLoaderUrls = { classpath };
String reloadableClassName = "it.tukano.Bingo";
for(int i = 0; i < 10; i++) {
Class<?> c = new URLClassLoader(classLoaderUrls).loadClass(reloadableClassName);
Runnable instance = (Runnable) c.newInstance();
instance.run();
Thread.sleep(10000);
}
}
}

Se durante l'esecuzione del programma cambiamo il contenuto della stringa TEST_MESSAGE in Bingo e ricompiliamo il file sorgente sostituendo il precedente file Bingo.class, il programma Main rifletterà quel cambiamento.

tylerdurden83
06-02-2010, 16:17
Grazie della risposta ora sono su cell dopo mi leggo tutto per bene, grazie 1000 ancora!

tylerdurden83
06-02-2010, 23:59
Direi che mi è rimasto solo un dubbio, ossia il meccanismo di delega del caricamento delle classi. Ho paura che usare new URLClassLoader, specialmente se non uso il costruttore (url, parentClassLoader), potrebbe non funzionare correttamente...

Ad esempio, quale è più corretto usare tra:

URLClassLoader ldr = new URLClassLoader(urls, getClass().getClassLoader());

e

URLClassLoader ldr = new URLClassLoader(urls);

Mi verebbe da dire il primo. Se uso il secondo per caricare una classe, e questa classe contiene altre classi, ldr proverà a caricare queste classi, e non avendo padri, le caricherà nuovamente di persona, invece di riusare quelle caricate in precedenza?

La cosa mi ha fatto ricordare di aver letto da qualche parte che uno consigliava di creare nel main thread un custom class loader, settarlo come "default" class loader del thread(e quindi del resto dell'applicazione), in modo che da quel momento in poi ogni classe che verrà caricata, passerà per questo custom class loader. Il custom class loader è fatto in modo da non delegare il caricamento di certe classi ai classloader superiori, e di passare il caricamento di altre ai class loader superiori.

PGI-Bis
07-02-2010, 00:27
Non c'è differenza tra i due (a meno che la classe del getClassLoader non sia caricata da un class loader personalizzato).

A prescindere, il problema non sussiste nel senso che URLCLassLoader delega il caricamento delle classi che non sono definite nei percorsi che gli affidi.

Cioè se la classe ricaricabile usa Object, l'Object è quello che già è in memoria. Idem per tutti quei tipi che la tua classe può voler usare ma che non sono soggetti a mutazione (e che quindi non saranno nella cartella "ricaricabile" ma faranno parte delle librerie principali del programma).

Tieni naturalmente presente che se la classe ricaricabile usa altre classi quelle classi devono trovarsi o nelle cartelle (o jar) usate dall'URLClassLoader, e saranno ricaricate, o nel classpath del programma, e non saranno ricaricate.

Puoi anche "imparentare" il tuo URLClassLoader con un altro URLClassLoader nel caso in cui tu non voglia includere nel classpath classi non ricaricabili.

Quando al classloader nel main farlo in generale non è consigliabile. La ragione principale sta nel fatto che il classloading è soggetto a restrizioni di sicurezza: l'utente può sempre applicare una policy restrittiva che manda a ramengo un programma solo perchè usa un classloader di cui magari non c'è bisogno.

Dal punto di vista pratico, la necessità sorge perchè in Java l'istanziazione (il new) è statica e monomorfa. Se un certo programma da qualche parte dice:

Object x = new Qualcosa();

l'unico modo "linguistico" per far sì che "new Qualcosa()" restituisca un'istanza di un sottotipo di "Qualcosa" è... cambiare il codice sorgente. Se non si ha accesso al codice sorgente l'alternativa è passare per il classloading e fare in modo che a "Qualcosa" corrisponda qualcos'altro.

Il che si può tranquillamente fare in un secondo momento. Qualsiasi programma Java è sempre avviabile da un altro programma Java e, in quanto precedente, l'altro programma può iniettare un suo classloader.

Per questo genere di manipolazione, tuttavia, oggi si usa la "byte code instrumentation" (cioè vai a mettere le mani direttamente nella JVM durante la sua esecuzione).

tylerdurden83
07-02-2010, 12:34
Continuo a prendere errore, ho scritto questo codice molto simile al tuo per fare un caso base...


package helloworld;

public class Hello {
public Hello(){
System.out.println("Hello Loaded!");
}
}



package mytestloader;

import java.net.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import helloworld.Hello;

public class Main {

public static void main(String[] args) {
try {
String path = "C:/Users/Costanza/Documents/NetBeansProjects/HelloWorld/build/classes/helloworld/Hello.class";
//String path = "C:/Users/Costanza/Documents/NetBeansProjects/HelloWorld/dist/HelloWorld.jar";
File file = new File(path);
System.out.println("Can the file be read? "+file.canRead());
URL classpath = file.toURI().toURL();
System.out.println("URL="+classpath);
URL[] classLoaderUrls = { classpath };
String reloadableClassName = "helloworld.Hello";
Class<?> c = new URLClassLoader(classLoaderUrls).loadClass(reloadableClassName);
Hello instance = (Hello) c.newInstance();


Il manifest di MyTestLoader.jar non ha riferimenti a HelloWorld.jar, solo il puntamento alla main class mytestloader.Main

Risultati:

- OK se lanciato con: java -cp "C:/myPath/HelloWorld.jar;MyTestLoader.jar" mytestloader.Main
- KO se lanciato con java -jar MyTestLoader.jar
ClassNotFoundException: helloworld.Hello
at mytestloader.Main.main(24) (ossia "Class<?> c = new URLClassLoader(classLoaderUrls).loadClass(reloadableClassName);")

Tu mi dirai, e ti credo, dovevi puntare il jar, non il class, con l'url passata al class loader...
Sempre riguardo il codice sopra, se commento la prima String path che punta al class e scommento la seconda che punta al jar, ho un risultato un po' strano...

L'output, lanciandolo sempre come java -jar MyTestLoader.jar, è, usando anche -verbose:


[Loaded java.security.Principal from shared objects file]
[Loaded mytestloader.Main from file:/D:/java%20test/MyTestLoader.jar]
[Loaded java.lang.IllegalAccessException from shared objects file]
[Loaded java.lang.InstantiationException from shared objects file]
[Loaded java.net.MalformedURLException from shared objects file]
Can the file be read? true
[Loaded java.net.URI from shared objects file]
[Loaded java.net.URI$Parser from shared objects file]
URL=file:/C:/Users/Costanza/Documents/NetBeansProjects/HelloWorld/dist/HelloWorl
d.jar
[Loaded helloworld.Hello from file:/C:/Users/Costanza/Documents/NetBeansProjects
/HelloWorld/dist/HelloWorld.jar]
Hello Loaded!
Exception in thread "main" java.lang.NoClassDefFoundError: helloworld/Hello
at mytestloader.Main.main(Main.java:27)(ossia "Hello instance = (Hello) c.newInstance();")
Caused by: java.lang.ClassNotFoundException: helloworld.Hello
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
... 1 more
[Loaded java.lang.Shutdown from shared objects file]
[Loaded java.lang.Shutdown$Lock from shared objects file]


Quindi non riesco a capire nè dove sta il mio errore rispetto al tuo codice, nè perchè alla fine sembra funzionare ma da cmq errore... :muro:

EDIT: ho aggiornato lo stacktrace con uno piu completo...

PGI-Bis
07-02-2010, 13:06
Gli URL accettati da URLClassLoader sono o file jar o cartelle. Non file class.

Gli url che denotano cartelle devono terminare con uno slash /.

Se vuoi caricare la classe Hello.class devi "puntare" l'URLClassLoader alla directory che contiene il package "helloworld", cioè:

"file:///C:/Users/Costanza/Documents/NetBeansProjects/HelloWorld/build/classes/"

Nel primo caso funziona perchè includi il jar nel classpath del programma (quindi carichi la classe ma non la puoi ricaricare perchè è soggetta a delega).

Nel secondo caso non funziona perche l'URL dell'URLClassLoader non è corretto.

Il terzo caso, quello bizzarro, è dovuto ad un conflitto tra classi ugualmente denominate ma caricate da due class loader diversi.

Quando in mytestloader.Main scriviamo:

Hello instance = (Hello) c.newInstance();

Le classi di nome "helloworld.Hello" sono due: la prima è quella del "c.newInstance", la seconda è quella che nel codice corrisponde al nome "Hello".

"c" è l'Hello del tuo URLClassLader. Questa viene caricata e istanziata correttamente (e infatti ti stampa "Hello loaded").

MA l'Hello della conversione (Hello) e del tipo "Hello" di instance è un'altra classe, è la classe "helloworld.Hello" il cui caricamento spetto al ClassLoader di mytestloader.Main.

Alla base del comportamente apparentemente strano sta il fatto che puoi compilare una classe (in questo caso mytestloader.Main) rispetto ad un certo classpath ottenendo un certo bytecode ed eseguire quel bytecode con un classpath diverso.

In generale devi evitare di menzionare nel codice che fa uso della classe ricaricabile il nome di questa classe. Vale a dire che in "mytestloader.Main" non dovrebbe mai apparire il nome "Hello".

tylerdurden83
07-02-2010, 13:19
In generale devi evitare di menzionare nel codice che fa uso della classe ricaricabile il nome di questa classe. Vale a dire che in "mytestloader.Main" non dovrebbe mai apparire il nome "Hello".

Ottimo, non avevo capito la questione del doppio caricamento...

Nel tuo esempio, hai evitato di mensionare Bingo perchè Bingo implementa Runnable.
Nel mio caso, come è più corretto risolvere secondo te?
Mi viene in mente, o interfaccia o definire un custom class loader che carichi personalmente sempre e solo lui certe classi, per evitare che Hello venga caricato da 2 class loaders ad es...

Nota: la necessità sorge dal seguente problema. Ho un programma/jar principale, facciamo MyMainJar.jar. Dal client lancio un jar chiamato updater.jar, che si connette ad un server via socket e provvede all'eventuale update di MyMainJar.jar. Terminato l'update, dentro il codice di updater.jar c'è il lancio di una nuova instanza di MyMainJar. Siccome MyMainJar.jar viene rimpiazzato dopo aver lanciato updater.jar, e potrebbe anche utilizzare librerie come jdbc che prima non usava, mi servirebbe appunto di "caricare" i jar dinamicamente, invece che al momento del java -cp updater.jar.

PGI-Bis
07-02-2010, 13:37
Una possibilità è quella di dichiarare un'interfaccia in una libreria separata. Questa interfaccia astrare le funzionalità minime garantire dal tuo modulo ricaricabile.

Potrebbe essere una cosa tipo:

package programma.updater;

public interface Updater {
void update();
}

Usi una libreria a parte perchè poi questa interfaccia ti serve per compilare sia il progetto principale sia il modulo del concreto uploader - che può benissimo essere un progetto remoto. Eviti di doverti portare in giro l'intero progetto nel caso in cui, ad esempio, l'updater sia mantenuto da qualcun'altro.

Nel programma principale apparirà sempre e solo questo "Updater". Nel programma del concreto "updater" la classe ricaricabile concretizzerà l'interfaccia Updater.

Il custom classloader lascialo perdere. I casi in cui è effettivamente necessario estendere un ClassLoader sono assolutamente marginali.

tylerdurden83
07-02-2010, 13:57
Perfetto grazie 1000, dovrebbe essere tutto :D
In caso dovessi imbattermi in altri problemi ti farò sapere, intanto ti ringrazio infinitamente per avermi aiutato prima a capire e poi a risolvere concretamente questo marasma!

tylerdurden83
08-02-2010, 01:54
Ummm in effetti un altro dubietto mi sta sorgento nella testolina... :mbe:

Aggiungere implements Updater alle mie classi dovrebbe essere facile, ma come mi comporto con il caricamento dinamico di classi/jar di terze parti, tipo driver jdbc per mysql/oracle?...

fero86
08-02-2010, 17:25
JNLP? :D

PGI-Bis
08-02-2010, 17:32
Se possono cambiare le metti tra gli URL del tuo URLClassLoader, se fanno parte del "core" del programma le metti nel programma.

Io direi che vanno tra le classi ri-caricabili se sono usate solo da queste ultime. Non avere particolare timore della quantità di classi da caricare o ricaricare. Basti pensare che il numero di classi richieste per il solo avvio della JVM è nell'ordine delle diverse migliaia e la procedura occupa frazioni di secondo.

tylerdurden83
09-02-2010, 17:57
Il mio dubbio più che altro era legato alle classi/librerie di terzi, fai ojdbc14.jar, che potrebbero un domani diventare necessarie all'applicazione.

Per fare un esempio:

ho un run.bat, nel run.bat aggiungo al classpath MyLibraries.jar (magari contenente anche l'interfaccia Updater di cui abbiamo discusso) e DownloadUpdates.jar, e per finire lancia downloadupdater.Main. Quest'ultima classe scarica in locale la nuova versione del programma principale da un server da qualche altra parte, via socket, fai MyMainProject.jar, se necessario aggiornarla naturalmente. Una volta scaricato il nuovo jar, carico le classi al suo interno, dinamicamente come abbiamo discusso in precedenza. Fin qui, funziona tutto. Metti tuttavia che un domani MyMainProject.jar inizia a usare il DB, e quindi gli serve anche ojdbc14.jar. Siccome questo non è presente sul client, downloadupdater.Main lo richiede e scarica correttamente dal server remoto.
A questo punto sorge un problema però. run.bat non ha esportato nel classpath ojdbc.jar, quindi mi farebbe comodo caricarlo dinamicamente dentro MyMainProject.jar. Tuttavia il trucchetto dell'interface non è applicabile, dato che non posso aggiungere implements Updater a codice non mio... Come posso fare?

Nuovamente grazie 1000 per il tuo enorme aiuto,
Rob

PGI-Bis
09-02-2010, 18:49
Non voglio dire che sia una soluzione generale ma non è insolito che un'applicazione desktop java abbia tre componenti:

un programma di avvio, chiamiamolo attivatore
un modulo centrale, il nucleo del programma vero e proprio
una serie di plug-in

il programma di avvio è un "compositore" e si occupa di installare gli aggiornamenti, rilevare i plug in e avviare il nucleo. Il nucleo installa i plug-in e avvia il programma.

L'attivatore e il nucleo esistono separatamente - nel senso che sono programmi separati - e in quanto separati possono aggiornarsi reciprocamente.

il tuo "run.bat" che stabilisce il classpath e avvia il programma mi sa tanto di attivatore.

Se così è allora ciò che fai nel caso in un aggiornamento del programma lo coinvolga è semplicemente cambiare il "run.bat".

Nota che questo cambiamento lo deleghi al nucleo. La procedura di avvio è cioè una cosa di questo genere:

1) parte l'attivatore (il run.bat): scarica il nuovo nucleo e i plug-in aggiornati con le loro dipendenze, avvia il nucleo e termina
2) parte il nucleo: scarica gli aggiornamenti dell'attivatore.
2.1) se ci sono aggiornamenti per l'attivatore, torna a 1
2.2) se non ci sono aggiornamenti per l'attivatore, installa i plug-in e prosegue

Non è l'unica soluzione. Ci sono ad esempio programmi che hanno due attivatori. Uno immutabile che ne scarica uno mutabile che scarica e applica gli aggiornamento del nucleo prima di avviarlo con i suoi plug-in.

Oppure puoi usare un attivatore e un file di configurazione (ma è una variante perchè la parte dell'attivatore che legge il file di configurazione è a tutti gli effetti un interprete, cioè un secondo attivatore).

Si tratta comunque di caricamento in senso stretto e non richiede interfacce o comunque tipi intermedi. La questione dell'interfaccia che si frappone tra il tipo usato e quello "vero" sorge solo quando hai la necessità di cambiare il comportamento del programma durante la sua esecuzione.

Tieni conto che se puoi distribuire il programma attraverso una rete (intranet o internet che sia) allora puoi usare il protocollo JNLP, come suggerito da fero, che usa un attivatore - è in effetti il primo di un attivatore a due stadi.

Tramite JNLP puoi in pratica assicurarti che ad ogni avvio del programma gli utenti abbiano a disposizione la versione più aggiornata del nucleo con tutte le librerie che questo richiede.

tylerdurden83
09-02-2010, 21:59
Ottimo as always, intanto ho trovato questo tutorial (http://www.informit.com/articles/article.aspx?p=25043&seqNum=3), vado a studiare!