PDA

View Full Version : [JAVA] Caricare dinamicamente un jar


VegetaSSJ5
27-09-2012, 16:15
Salve a tutti! Devo realizzare un sistema a "plugin". Finora ho sviluppato tutto in un'unica applicazione, separando in package differenti le classi che andranno a costituire il mio plugin.
Quindi nel flusso della mia applicazione principale ad un certo punto ho un a cosa del genere:
InputPlugin ip = new NomePlugin(parameters);
Dove
InputPlugin è un'interfaccia
NomePlugin è il nome della classe che implementa quell'interfaccia
parameters è un'istanza di tipo Map<String, String>

Ora mi trovo a dover staccare questi plugin e metterli ognuno in differenti jar.
Ed in effetti ho fatto così. A questo punto nella mia applicazione principale non potrò più avere un'istruzione come quella riportata precedentemente, ma il caricamento dovrà essere dinamico in base al nome del plugin che intendo caricare. Ho fatto come segue (ho trovato il codice in rete):
String inputPluginJarPath = "jar:file://" + inputPluginPath + "!/";
ClassLoader inputPluginLoader = URLClassLoader.newInstance(new URL[] { new URL(inputPluginJarPath) });
Class<?> myclassloader = inputPluginLoader.loadClass(inputPluginName);
InputPlugin inputPlugin = (InputPlugin) myclassloader.newInstance();
L'ultima istruzione mi dà però una InstantiationException, senza ulteriori informazioni.
Ho creato anche un costruttore vuoto e senza parametri nella classe che dovrei caricare, ma senza risolvere nulla...
In sostanza, come posso avere nella variabile inputPlugin un'istanza della classe di cui conosco il nome (e il relativo nome del jar che la contiene)?

Grazie.

demos88
27-09-2012, 18:03
Per un progetto universitario ho fatto una cosa molto simile (anzi praticamente uguale...).
Ho fatto una cosa del genere (le strane chiamate toURI toURL dipendono dal contesto in cui è sviluppato il codice, con librerie sviluppate internamente):

URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{engineJarFile.toURI().toURL()}, this.getClass().getClassLoader()); //rispetto al tuo codice cambia che ho specificato il classloader

Class<?> engineToLoad = Class.forName(engine.getEngineClassPath(), false, classLoader); //ottengo la classe che mi interessa con il metodo forName (http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#forName%28java.lang.String,%20boolean,%20java.lang.ClassLoader%29)

Constructor<?> constructor = engineContainer.getEngineClass().getConstructor(new Class[]{}); //ottengo il costruttore standard (senza parametri) della classe

IFace oggetto = (IFace) constructor.newInstance(new Object[]{}); //istanzio la classe usando il costruttore standard (senza parametri)


A questo punto l'oggetto lo uso come userei qualsiasi oggetto che implementa IFace (dopo aver controllato con metodi reflection che effettivamente implementi l'interfaccia IFace), nel tuo caso potrebbe essere l'interfaccia InputPlugin.

Vedi se riesci a cavarne fuori qualcosa, buona fortuna :D

VegetaSSJ5
27-09-2012, 20:45
RISOLTO
Mi è bastato cambiare le seguenti righe... Da come mi sembra di capire il problema è la stringa del path del jar.
String inputPluginClassName = inputPluginPackage+"."+pluginName+"InputPlugin";
File inputPluginJarFile = new File(inputPluginPath);
ClassLoader classLoader = URLClassLoader.newInstance(new URL[] { inputPluginJarFile.toURI().toURL() });
inputPlugin = (InputPlugin) classLoader.loadClass(inputPluginClassName).newInstance();
inputPlugin.initialize(parameters);

balth@zar
01-10-2012, 14:36
Immagino che tu abbia cambiato anche il costruttore della tua classe plugin, inserendo un costruttore di default.
Il metodo Class.newInstance() funziona solo se la classe ha un costruttore di default, altrimenti lancia un InstantiationException.
Per istanziare tramite reflection una classe con un costruttore che prende degli argomenti devi prima ottenere il costruttore giusto tramite Class.getConstructor(...) e su questo puoi eseguire Constructor.newInstance(...) passando un'array di oggetti che rappresentano gli argomenti del costruttore.