2013-04-19 14 views
31

Sto provando a creare un sistema di plugin per la mia applicazione e voglio iniziare con qualcosa di semplice. Ogni plug-in deve essere imballata in un file .jar e implementare l'interfaccia SimplePlugin:Caricamento dinamico dei jar del plugin con ServiceLoader

package plugintest; 

public interface SimplePlugin { 
    public String getName(); 
} 

Ora ho creato un'implementazione di SimplePlugin, confezionato in un .jar e metterlo nel plugin/sottodirectory dell'applicazione principale :

package plugintest; 

public class PluginTest implements SimplePlugin { 
    public String getName() { 
     return "I'm the plugin!"; 
    } 
} 

nel ricorso principale, voglio ottenere un'istanza di PluginTest. Ho provato due alternative, entrambi con java.util.ServiceLoader.

1. dinamicamente estende il percorso di classe

Questo utilizza l'hack conosciuti per usare riflessione sul caricatore classe sistema per evitare incapsulamento, al fine di aggiungere URL s il classpath. viene aggiunto

package plugintest.system; 

import plugintest.SimplePlugin; 

import java.io.File; 
import java.io.IOException; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.Iterator; 
import java.util.ServiceLoader; 

public class ManagePlugins { 
    public static void main(String[] args) throws IOException { 
     File loc = new File("plugins"); 
     extendClasspath(loc); 

     ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class); 
     Iterator<SimplePlugin> apit = sl.iterator(); 
     while (apit.hasNext()) 
      System.out.println(apit.next().getName()); 
    } 

    private static void extendClasspath(File dir) throws IOException { 
     URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 
     URL urls[] = sysLoader.getURLs(), udir = dir.toURI().toURL(); 
     String udirs = udir.toString(); 
     for (int i = 0; i < urls.length; i++) 
      if (urls[i].toString().equalsIgnoreCase(udirs)) return; 
     Class<URLClassLoader> sysClass = URLClassLoader.class; 
     try { 
      Method method = sysClass.getDeclaredMethod("addURL", new Class[]{URL.class}); 
      method.setAccessible(true); 
      method.invoke(sysLoader, new Object[] {udir}); 
     } catch (Throwable t) { 
      t.printStackTrace(); 
     } 
    } 
} 

Il/directory plugins come previsto (come si può controllare chiamando sysLoader.getURLs()), ma poi l'iteratore in oggetto ServiceLoader è vuoto.

2. Uso URLClassLoader

Questo utilizza un'altra definizione di ServiceLoader.load con un secondo argomento della classe ClassLoader.

package plugintest.system; 

import plugintest.SimplePlugin; 

import java.io.File; 
import java.io.FileFilter; 
import java.io.IOException; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.Iterator; 
import java.util.ServiceLoader; 

public class ManagePlugins { 
    public static void main(String[] args) throws IOException { 
     File loc = new File("plugins"); 

     File[] flist = loc.listFiles(new FileFilter() { 
      public boolean accept(File file) {return file.getPath().toLowerCase().endsWith(".jar");} 
     }); 
     URL[] urls = new URL[flist.length]; 
     for (int i = 0; i < flist.length; i++) 
      urls[i] = flist[i].toURI().toURL(); 
     URLClassLoader ucl = new URLClassLoader(urls); 

     ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class, ucl); 
     Iterator<SimplePlugin> apit = sl.iterator(); 
     while (apit.hasNext()) 
      System.out.println(apit.next().getName()); 
    } 
} 

Ancora una volta, l'iteratore non ha mai un elemento "successivo".

C'è sicuramente qualcosa che mi manca poiché è la prima volta che sto "giocando" con percorsi di classe e caricamento.

+0

perché non utilizzare una semplice libreria con il supporto di riflessione? come la configurazione comune di apache con il supporto per i bean: http://commons.apache.org/proper/commons-configuration/userguide/howto_beans.html#Declaring_and_Creating_Beans –

+0

@omerschleifer Perché, come ho detto, è la prima volta che gioco con questo genere di cose e voglio imparare come funzionano. In secondo luogo, il tuo consiglio è il benvenuto, ma le biblioteche hanno il problema comune che possono fare molto più di quello che vuoi fare, quindi le cose sono spesso più complicate del necessario. Non so se è il caso, ma voglio risolverlo in una libreria esterna solo se è la mia ultima possibilità. – MaxArt

+1

Se questo è per scopi didattici, divertiti. ma per quanto riguarda la complessità, la riflessione è molto più facile da eseguire e padroneggiare utilizzando una libreria semplice come quella che ti ho inviato piuttosto che inventare la ruota da solo. La riflessione è, dopo tutto, un'area ben esplorata. buona fortuna –

risposta

33

Il problema era molto semplice. E stupido. Nei file plugin .jar il file /services/plugintest.SimplePlugin non era presente nella directory META-INF, quindi ServiceLoader non è stato in grado di identificare i jar come servizi e caricare la classe.

Questo è praticamente tutto, il secondo (e più pulito) modo funziona come un fascino.

1

La soluzione per il vostro concetto di applicazione è stata già descritta in Oracle documentazione (tra cui JAR Caricamento dinamico)

Creazione di applicazioni estensibile con Java Platform http://www.oracle.com/technetwork/articles/javase/extensible-137159.html

sul fondo di questo articolo si vuole trovare i link a

  • codice sorgente dell'esempio
  • Javadoc ServiceLoader API

Secondo me è meglio modificare leggermente l'esempio di Oracle piuttosto che reinventare la ruota come diceva Omer Schleifer.

1

A partire da Java 9 il servizio di scansione sarà molto più semplice ed efficiente. Non c'è più bisogno di META-INF/services.

Nell'interfaccia dichiarazione del modulo di dichiarazione:

uses com.foo.spi.Service; 

E nel modulo del provider:

provides com.foo.spi.Service with com.bar.ServiceImplementation 
Problemi correlati