Newsgroup: it.comp.java
Titolo: ClassLoader Custom in applicazione multi-thread

Ciao a tutti,
sto scrivendo un'applicazione che necessità di poter caricare
dinamicamente delle classi che non erano previste all'istante
dell'avvio della stessa. Per far ciò, ovviamente, ho scritto un
ClassLoader custom estendendo URLClassLoader.
Compito di un class loader è mantenere in memoria i dati della classe
binaria.
La domanda è: in un contesto multi-thread il class loader custom deve
essere un singleton?
Altrimenti, dico io, ogni thread allocherebbe memoria per memorizzare
lo stesso codice binario delle classi forniti a un determinato
"classpath".
Non credo che sia logico distruggere l'istanza al termine di
esecuizione del thread e ricreare un'istanza di classloader a ogni
richiesta di un thread. Non si avrebbe il vantaggio di memorizzare il
codice binario delle classi.
Sto ragionando giusto?
Grazie
Mr. Deckard

il 25.03.2010 21.48, Scrive Mr. Deckard 43207224:

Io prima di scrivere un classloader guarderei java.util.ServiceLoader, 
che mi sembra che calzi a pennello per il tuo scopo.

On 26 Mar, 02:12, "Dr.Ugo Gagliardelli"

Grazie per l'indicazione, mi metto a studiare...
Ma le mie considerazioni del mio precedente post sono corrette secondo
voi?
Grazie ancora,
Mr. Deckard

il 26.03.2010 10.07, Scrive Mr. Deckard 43207224:

Sul fatto del singleton? Mi viene da dire che e' obbligato ad esserlo. 
Un ClassLoader *custom* deve per forza avere quello di sistema tra i 
suoi parent per cui tanto vale istanziarlo all'avvio della vm. L'unico 
caso che mi viene in mente (trattandosi della *mia* mente, non escludo 
che si possa pensare anche ad altre casistiche) di istanza relativa ad 
un solo contesto, e' quando registri un classloader come ObjectName nel 
ClassLoaderRepository di un servizio JMX.

On 26 Mar, 11:53, "Dr.Ugo Gagliardelli"

Ciao,
mi sono documentato seguendo il tuo consiglio. ottimo, seguendo la
documentazione sono riuscito a implementare un'architettura
pluggabile...
Quindi posso aggiungere a runtime dei file jar che importano le nuove
classi. ma adesso rimane il problema per me fondamentale come le
classi che sono già state caricate. Insomma poter aggiornare le classi
a run-time...
Grazie ancora per il consiglio.
Mr. Deckard

 Mr. Deckard
La vedo dura, non saprei come fare a modificare classi a runtime. direi 
che non si possa. Diciamo che, ad esempio, il debugger dell'eclipse ti 
permette qualcosina, ma se cambi la firma di un metodo ti fa' riavviare.

On 26 Mar, 18:25, "Dr.Ugo Gagliardelli"

Capisco, ma allora l'unico modo è avere un ClassLoader
personalizzato???
Grazie

il 26.03.2010 18.37, Scrive Mr. Deckard 43207224:

Eventualmente si', pero' cosa gli fai fare? Tutte le volte che il 
classloader viene invocato per caricare una classe, dodrebbe controllare 
se e' stata modificata rispetto alla versione cacheata; a questo punto 
la cahe non serve più a niente, devi sempre ricorrere al file-system per 
fare il load di nuovo. Potrebbe essere piuttosto penalizzante.

Mi sono interessato pure io alla questione, per iniettare dinamicamente 
delle patch in un sw mission critical senza dover quindi fare dei 
fermi. Na caciara... Leggi questo articolo: 
http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html
 

On 26 Mar, 19:46, "Dr.Ugo Gagliardelli"

Hai ragione,
in realtà, l'idea è che il load venga invocato solamente a seguito di
un evento applicativo (ad esempio aggiornamento libreria...).
Che ne pensi?
Io sto provando a implementare un ClassLoader personalizzato
(Singleton)... magari posso postarlo e facciamo un'analisi critica di
ciò che ho scritto?
Sei molto gentile.
Grazie

il 26.03.2010 19.52, Scrive Booo 43207224:

Rispecchia esattamente quello che penso io. Ma ponevo soprattutto 
l'attenzione sul fatto che ad un serto punto devi prndere una decisione 
se ricaricare una classe o no. Potresti versionare le nuove classi in 
qualche modo, ma se sono di una nuova versione lo sai solo dopo averle 
gia' caricate. Basarsi sul file-system non e' particolarmente 
affidabile, poiche' dipende soprattutto dal files system, ed anche 
caricando la classe da un jar magari perche' ti accorgi che il file e' 
più nuovo, potresti comunque ricaricare una classe identica ad una 
precedente semplicemente perche' e' stata ricompilata. Il tutto con un 
dispendio di energie che aumentano piu' l'applicazione e' grande in 
termini di numero di classi.
Quando ho scritto il mio classloader per registrarlo in un JMX, mi ero 
posto il problema di renderlo dinamico, ma poi non ho trovato soluzioni 
praticabili/soddisfacenti.

 ciò che ho scritto?
Se vuoi ti posto il mio, e' un po' incasinato perche' oltre ad estendere 
ClassLoader implementa anche DynamicMBean, inoltre utilizza un ClassPath 
  ed un Repository estesi dal package com.sun.org.apache.bcel.internal. 
Lo so non e' bello, pero' in questo modo me la sono cavata con tre 
classi, una classe interna per estendere StandardMBean,  un'interfaccia 
ed una quindicina di metodi ed ho ridotto la gerarchia dei parent a tre: 
system/parent/current che mi ha garantito di dover caricare solo le 
classi tramite la root dei package interessati.

 praticabili/soddisfacenti.
Grazie Ugo, Grazie Booo,
allora il mio contesto è questo. Teoricamente un aggiornamento non
dovrebbe causare il fermo dell'applicazione (AS). E potrebbe essere
sostituito un intero jar, senza sapere quale siano le classi
modificate...
Non mi è chiaro come risolvere questo punto? Non è possibile forzare
il rficaricamento di tutto il jar? Altrimenti? E' questo il punto che
mi sfugge.
Grazie mille

il 26.03.2010 21.34, Scrive Mr. Deckard 43207224:

Beh, all'interno di un application server il classloader e' quello del 
servlet engine che, il websphere, ad esempio, ricarica gia' di suo le 
classi nuove/modificate. Direi che anche il tomcat faccia qualcosa del 
genere, ma non lo so di preciso.

 Grazie mille
Ciao ragazzi,
vi prego di non lanciare urli di disgusto... vi posto una mia
soluzione e avere un giudizio critico da voi:
public class MyClassLoader extends URLClassLoader {
    private static Logger logger =
Logger.getLogger(MyClassLoader.class.getName());
    private static MyClassLoader netClassLoader = null;
    private static ArrayList<URL> alArls = new ArrayList<URL>();
    private MyClassLoader(URL[] urls, ClassLoader classLoaderParent) {
        super(urls, classLoaderParent);
    }
    public static synchronized MyClassLoader getInstance() throws
Exception {
        ClassLoader classLoaderParent =
ClassLoader.getSystemClassLoader().getParent();
        if (netClassLoader != null) {
            return netClassLoader;
        } else {
            if(alArls.size() >0){
               URL[] u;
               u = new URL[3];
                netClassLoader = new MyClassLoader(alArls.toArray(u),
classLoaderParent);
            }
            else{
                throw new Exception("Aggiungere un path");
            }
            return netClassLoader;
        }
    }
    /**
     * Adds the jars in the given directory to classpath
     * @param directory
     * @throws IOException
     */
    public static void addDirToClasspath(File directory) throws
IOException {
        if (directory.exists()) {
            File[] files = directory.listFiles();
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                alArls.add(file.toURI().toURL());
            }
        } else {
            logger.warning("The directory \"" + directory + "\" does
not exist!");
        }
    }
    @Override
    public Class<?> loadClass(String name) throws
ClassNotFoundException {
        // First check whether it's already been loaded, if so use it
        Class loadedClass = findLoadedClass(name);
        System.out.println("name " + name + " loadedClasse " +
loadedClass);
        // Not loaded, try to load it
        if (loadedClass == null) {
            try {
                // Ignore parent delegation and just try to load
locally
                loadedClass = findClass(name);
            } catch (ClassNotFoundException e) {
                // Swallow exception - does not exist locally
            }
            // If not found locally, use normal parent delegation in
URLClassloader
            if (loadedClass == null) {
                // throws ClassNotFoundException if not found in
delegation hierarchy at all
                loadedClass = super.loadClass(name);
            }
        }
        // will never return null (ClassNotFoundException will be
thrown)
        return loadedClass;
    }
    public void clearClassLoader(){
        this.netClassLoader = null;
    }
}
Come vedete ho trasformato il ClassLoader Custom (MyClassLoader) in
una classe singleton (lo so non è la miglore implementazione di
singleton).
Quando l'applicativo riceve la notifica che è stato aggiornata una
libreria viene invocato il metodo clearClassLoader che distrugge
l'unica istanza del ClassLoader costringendo a ricaricare la/le
librerie.
So bene che questo potrebbe portare a un sovraccarico costringendo il
ricaricamento di tutte le classi di tutti i possibili jar. Ma potremmo
separare i vari moduli dell'applicazione su più ClassLoader Custom che
puntino a path separati.
Si potrebbe inoltre prevedere dei meccanismi di ricaricamento sulla
singola classe o su packages.
Che ne pensate? Sono gradite le vostre critiche, osservazioni e
possibili bug risultanti da questa implementazione.
Grazie mille
MR. Deckard

 ricaricamento di tutte le classi di tutti i possibili jar. 
Questo e' l'efftto sulle performance. Ma l'effetto sugli oggetti 
referenziati che sono in giro non lo hai considerato.
Ogni volta che carichi una classe di nuovo, non e' piu' la stessa di 
prima anche se ha la stessa firma, quindi devi aspettarti dei 
classcastexception in giro, che eviti solo facendo un cast espicito ad 
un interface, sempre che nel frattempo non sia stata modificata anch'essa.

On 27 Mar, 14:01, "Dr.Ugo Gagliardelli"

Grazie Ugo,
grazie per alimentare il confronto su questo tema da me posto.
Se posso approfittare delle tue osservazioni, provo a illustrarti il
mio contesto.
Nel mio caso, i ".jar" espongono una libreria di funzioni che
implementano alcune determinate interafacce.
Il "motore" che utilizza la libreria di funzioni crea gli oggetti per
mezzo di reflection. Le interafacce sono un elemento invariante
(almeno su finestre temporali sufficientemente lunge, fosse vero il
contrario sospetterei un problema id progettazione).
Appena creata l'istanza il motore invoca un determinato metodo. Anche
in questo contesto avrei il problema di "classcastexception" un attimo
dopo aver aggiornato il mio classloader?
Grazie
Mr. Deckard

 anch'essa.
Infatti non si è letto l'articolo che avevo linkato... ;-)

il 27.03.2010 15.08, Scrive Mr. Deckard 43208800:

Diciamo che non e' semplice predeterminare il comportamento del 
classloader, io stavo pensando ad uno scenario di questo genere:
- unPippo e' un'istanza di Pippo.
- Pippo ha un metodo setPluto(Pluto unPluto)
- la classe Pluto viene aggiornata dal classloader
- l'applicazione invoca unPippo.setPluto(new Pluto());
- il classloader invoca il load per la classe Pluto
- ti ritrovi con un ClassCastException in quanto Pluto != Pluto
per risolvere questo tipo di scenario, ritengo che occorra che:
- Pluto implements IPluto
- setPluto(IPluto unPluto)
- load ritorna un IPluto e non un Pluto
se e' corretto cio' che penso, direi che una simile implementazione di 
classloader, dipenda in definitiva dalle classi che deve caricare, per 
cui tradisce lo scopo del classloader in quanto e' costretto a conoscere 
a priori le classi che deve caricare. La conseguenza immediata e' che la 
nuova istanza di ClassLoader dovra' necessariamente essere ricaricata 
dal jar in quanto le potenziali nuove classi potranno essere conosciute 
solo da un nuovo classloader. Mi sembra che, dal punto di vista della 
manutenzione di un simile pacchetto, sia un'operazione un po' troppo 
laboriosa.

 Mr. Deckard
Hai provato a studiare OSGi [1]?
E' stato progettato espressamente per avere "moduli" caricabili
e sostituibili dinamicamente.
I container che vanno per la maggiore sono Felix di ASF [2]
ed Equinox di Eclipse [3].
Se l'idea ti stuzzicasse, ti consiglio di concentrarti sui
Declarative Services, che aiutano molto ad evitare
"boilerplate code" ed a iniettare le dipendenze.
Ciao,
  Guido
[1]: 
http://www.osgi.org/Main/HomePage
[2]: 
http://felix.apache.org/site/index.html
[3]: 
http://www.eclipse.org/equinox/