java jar classpath serviceloader

java - Carga dinámica de archivos jar usando ServiceLoader



classpath (3)

A partir de Java 9, el servicio de escaneo será mucho más fácil y eficiente. No más necesidad de META-INF/services .

En la declaración del módulo de interfaz, declare:

uses com.foo.spi.Service;

Y en el módulo del proveedor:

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

Intento crear un sistema de complemento para mi aplicación, y quiero comenzar con algo simple. Cada complemento debe estar empaquetado en un archivo .jar e implementar la interfaz SimplePlugin :

package plugintest; public interface SimplePlugin { public String getName(); }

Ahora he creado una implementación de SimplePlugin , empaquetada en .jar y puesta en el plugin / subdirectorio de la aplicación principal:

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

En la aplicación principal, quiero obtener una instancia de PluginTest . He intentado con dos alternativas, ambas usando java.util.ServiceLoader .

1. Extender dinámicamente el classpath

Esto utiliza el truco conocido para usar la reflexión en el cargador de clases del sistema para evitar la encapsulación, con el fin de agregar URL s el classpath.

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(); } } }

El directorio plugins / se agrega como se espera (como se puede verificar llamando a sysLoader.getURLs() ), pero luego el iterador proporcionado por el objeto ServiceLoader está vacío.

2. Usando URLClassLoader

Esto usa otra definición de ServiceLoader.load con un segundo argumento de la clase 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()); } }

Una vez más, el iterador nunca tiene un elemento "siguiente".

Seguramente hay algo que me falta, ya que es la primera vez que estoy "jugando" con las rutas de clases y la carga.


El problema fue muy simple. Y estúpido. En los archivos .jar del complemento /services/plugintest.SimplePlugin archivo /services/plugintest.SimplePlugin dentro del directorio META-INF , por lo que el ServiceLoader no pudo identificar los archivos /services/plugintest.SimplePlugin como servicios y cargar la clase.

Eso es prácticamente todo, el segundo (y más limpio) modo funciona como un encanto.


La solución para su concepto de aplicación ya se ha descrito en Oracle Documentation (incluida la carga dinámica de JAR)

Creación de aplicaciones extensibles con la plataforma Java http://www.oracle.com/technetwork/articles/javase/extensible-137159.html

en la parte inferior del artículo encontrarás enlaces a

  • código fuente del ejemplo
  • API de Javadoc ServiceLoader

En mi opinión, es mejor modificar ligeramente el ejemplo de Oracle que reinventar la rueda, como dijo Omer Schleifer.