java classloader serviceloader

Java ServiceLoader con múltiples Classloader



(5)

¿Cuáles son las mejores prácticas para usar ServiceLoader en un entorno con múltiples ClassLoaders? La documentación recomienda crear y guardar una sola instancia de servicio en la inicialización:

private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);

Esto inicializaría el ServiceLoader utilizando el cargador de clases de contexto actual. Ahora supongamos que este fragmento está contenido en una clase cargada utilizando un cargador de clases compartido en un contenedor web y varias aplicaciones web quieren definir sus propias implementaciones de servicios. Estos no se recogerán en el código anterior, incluso podría ser posible que el cargador se inicialice utilizando el primer cargador de clases de contexto de webapps y proporcione la implementación incorrecta a otros usuarios.

Siempre la creación de un nuevo ServiceLoader parece un desperdicio en cuanto a rendimiento, ya que tiene que enumerar y analizar archivos de servicio cada vez. Editar: Esto incluso puede ser un gran problema de rendimiento como se muestra en esta respuesta con respecto a la implementación XPath de Java .

¿Cómo manejan esto otras bibliotecas? ¿Almacenan en caché las implementaciones por cargador de clases, ¿vuelven a rastrear su configuración cada vez o simplemente ignoran este problema y solo funcionan para un cargador de clases?


¿Has intentado utilizar la versión de dos argumentos para que puedas especificar qué cargador de clases usar? Es decir, java.util.ServiceLoader.load(Class, ClassLoader)


Esta pregunta parece ser más complicada de lo que había anticipado. Según lo veo, hay 3 estrategias posibles para tratar con ServiceLoaders.

  1. Use una instancia de ServiceLoader estática y solo admita la carga de clases desde el mismo cargador de clases que el que contiene la referencia de ServiceLoader. Esto funcionaría cuando

    • La configuración y la implementación del servicio se encuentran en un cargador de clases compartido y todos los cargadores de clase secundarios están utilizando la misma implementación. El ejemplo en la documentación está orientado hacia este caso de uso.

      O

    • La configuración e implementación se implementan en cada cargador de clases hijo y se implementan a lo largo de cada aplicación web en WEB-INF/lib .

    En este escenario, no es posible implementar el servicio en un cargador de clases compartido y dejar que cada aplicación web elija su propia implementación de servicio.

  2. Inicialice el ServiceLoader en cada acceso que pase el cargador de clases de contexto del subproceso actual como el segundo parámetro. Este enfoque se toma con las API de JAXP y JAXB, aunque usan su propia implementación de FactoryFinder en lugar de ServiceLoader. Por lo tanto, es posible agrupar un analizador xml con una aplicación web y hacer que se DocumentBuilderFactory#newInstance automáticamente, por ejemplo, mediante DocumentBuilderFactory#newInstance .

    Esta búsqueda tiene un impacto en el rendimiento , pero en el caso de analizar XML el tiempo de búsqueda, la implementación es pequeña en comparación con el tiempo necesario para analizar realmente un documento xml. En la biblioteca, imagino que la fábrica en sí es bastante simple, por lo que el tiempo de búsqueda dominaría el rendimiento.

  3. De alguna manera, almacenar en caché la clase de implementación con el cargador de clases de contexto como clave. No estoy del todo seguro de si esto es posible en todos los casos anteriores sin causar pérdidas de memoria.

En conclusión, probablemente ignore este problema y requiera que la biblioteca se implemente dentro de cada aplicación web, es decir, la opción 1b anterior.


Me gusta mucho la respuesta de Neil en el enlace que agregué en mi comentario. Debido a que tengo las mismas experiencias en mi proyecto reciente.

"Otra cosa a tener en cuenta con ServiceLoader es intentar abstraer el mecanismo de búsqueda. El mecanismo de publicación es bastante bueno, limpio y declarativo. Pero la búsqueda (a través de java.util.ServiceLoader) es tan fea como el infierno, se implementó como una ruta de clases escáner que se descompone horriblemente si coloca el código en cualquier entorno (como OSGi o Java EE) que no tenga visibilidad global. Si su código se enreda con eso, entonces tendrá dificultades para ejecutarlo en OSGi más adelante. escribir una abstracción que puedas reemplazar cuando llegue el momento ".

De hecho, conocí este problema en el entorno OSGi, en realidad es solo un eclipse en nuestro proyecto. Pero afortunadamente lo arreglé de manera oportuna. Mi solución es usar una clase del plugin que quiero cargar, y obtener classLoader de él. Esa será una solución válida. No usé el ServiceLoader estándar, pero mi proceso es bastante similar, uso propiedades para definir las clases de complemento que necesito cargar. Y sé que hay otra forma de conocer el cargador de clases de cada complemento. Pero al menos no necesito usar eso.

Honestamente, no me gustan los genéricos usados ​​en ServiceLoader. Debido a que limitaba que un solo ServiceLoader solo puede manejar clases para una interfaz. Bueno, ¿es realmente útil? En mi implementación, no te obliga a esta limitación. Solo uso una implementación de cargador para cargar todas las clases de complementos. No veo la razón para usar dos o más. Debido a que el consumidor puede saber a partir de los archivos de configuración sobre las relaciones entre las interfaces y la implementación.


Mu.

En un sistema 1x WebContainer <-> Nx WebApplication, el ServiceLoader instanciado en el WebContainer no recogerá ninguna clase definida en WebApplications, solo aquellas en el contenedor. Un ServiceLoader instanciado en una aplicación web detectará las clases definidas en la aplicación además de las definidas en el contenedor.

Tenga en cuenta que las aplicaciones web necesitarán mantenerse separadas, están diseñadas de esa manera, las cosas se romperán si lo intenta y evita eso, y no son el método y el sistema disponibles para extender el contenedor. Si su biblioteca es un simple tarro, simplemente suelte en la carpeta de extensión apropiada del contenedor.


Personalmente, no me gusta el ServiceLoader bajo ninguna circunstancia. Es lento e innecesariamente derrochador y hay poco que puede hacer para optimizarlo.

También me parece un poco limitado: realmente tienes que salir de tu camino si quieres hacer algo más que buscar solo por tipo.

ResourceFinder de xbean-finder

  • ResourceFinder es un archivo java autónomo capaz de reemplazar el uso de ServiceLoader. Copiar / pegar reutilizar no es problema. Es un archivo java y tiene licencia ASL 2.0 y está disponible en Apache.

Antes de que nuestra atención se alargue demasiado, así es como puede reemplazar un ServiceLoader

ResourceFinder finder = new ResourceFinder("META-INF/services/"); List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

Esto encontrará todas las implementaciones de META-INF/services/org.acme.Plugin en su classpath.

Tenga en cuenta que en realidad no instancia todas las instancias. Elija la (s) que desea y usted es una newInstance() llamada para no tener una instancia.

¿Por qué es esto bueno?

  • ¿Qué tan difícil es llamar a newInstance() con el manejo de excepciones adecuado? No dificil.
  • Tener la libertad de instanciar solo los que quieres es agradable.
  • ¡Ahora puedes apoyar args de constructor!

Enfoque de búsqueda estrecho

Si desea verificar URL específicas, puede hacerlo fácilmente:

URL url = new File("some.jar").toURI().toURL(); ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

Aquí, solo se buscará el ''some.jar'' en cualquier uso de esta instancia de ResourceFinder.

También hay una clase de conveniencia llamada UrlSet que puede hacer que la selección de URLs de classpath sea muy fácil.

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); UrlSet urlSet = new UrlSet(webAppClassLoader); urlSet = urlSet.exclude(webAppClassLoader.getParent()); urlSet = urlSet.matching(".*acme-.*.jar"); List<URL> urls = urlSet.getUrls();

Estilos alternos de "servicio"

Supongamos que desea aplicar el concepto de tipo de ServiceLoader para rediseñar el manejo de URL y buscar / cargar el java.net.URLStreamHandler para un protocolo específico.

A continuación, le mostramos cómo puede diseñar los servicios en su classpath:

  • META-INF/java.net.URLStreamHandler/foo
  • META-INF/java.net.URLStreamHandler/bar
  • META-INF/java.net.URLStreamHandler/baz

Donde foo es un archivo de texto plano que contiene el nombre de la implementación del servicio igual que antes. Ahora di que alguien crea una URL foo://... Podemos encontrar la implementación de eso rápidamente, a través de:

ResourceFinder finder = new ResourceFinder("META-INF/"); Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class); Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

Estilos alternos de "servicio" 2

Digamos que quería poner algo de información de configuración en su archivo de servicio, por lo que contiene más que solo un nombre de clase. Aquí hay un estilo alternativo que resuelve servicios a archivos de propiedades. Por convención, una clave serían los nombres de clase y las otras claves serían propiedades inyectables.

Entonces, aquí el red es un archivo de propiedades

  • META-INF/org.acme.Plugin/red
  • META-INF/org.acme.Plugin/blue
  • META-INF/org.acme.Plugin/green

Puedes buscar cosas de manera similar a como antes.

ResourceFinder finder = new ResourceFinder("META-INF/"); Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName()); Properties redDefinition = plugins.get("red");

A continuación, le mostramos cómo puede usar esas propiedades con xbean-reflect , otra pequeña biblioteca que le puede proporcionar IoC sin marco. Usted simplemente le da el nombre de clase y algunos pares de valores de nombre y construirá e inyectará.

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString()); recipe.setAllProperties(redDefinition); Plugin red = (Plugin) recipe.create(); red.start();

Así es como puede verse "deletreado" en forma larga:

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin"); recipe.setProperty("myDateField","2011-08-29"); recipe.setProperty("myIntField","100"); recipe.setProperty("myBooleanField","true"); recipe.setProperty("myUrlField","http://www..com"); Plugin red = (Plugin) recipe.create(); red.start();

La biblioteca xbean-reflect es un paso más allá de la API de JavaBeans incorporada, pero es un poco mejor sin necesidad de ir hasta un framework completo de IoC como Guice o Spring. Es compatible con los métodos de fábrica y args constructor y setter / campo de inyección.

¿Por qué el ServiceLoader es tan limitado?

El código obsoleto en la JVM daña el lenguaje Java en sí. Muchas cosas se recortan hasta el hueso antes de agregarlas a la JVM, porque no puede recortarlas después. El ServiceLoader es un excelente ejemplo de eso. La API es limitada y la implementación de OpenJDK está en alrededor de 500 líneas, incluido javadoc.

No hay nada sofisticado allí y reemplazarlo es fácil. Si no funciona para usted, no lo use.

Alcance de Classpath

Dejando a un lado las API, en la pura practicidad, restringir el alcance de las URL buscadas es la verdadera solución a este problema. Los Servidores de aplicaciones tienen bastantes URLs solos, sin incluir los archivos jar en su aplicación. Tomcat 7 en OSX, por ejemplo, tiene aproximadamente 40 ~ URLs en el StandardClassLoader solo (este es el padre de todos los cargadores de clases de aplicaciones web).

Cuanto más grande sea tu servidor de aplicaciones, más tiempo llevará incluso una simple búsqueda.

El almacenamiento en caché no ayuda si tiene la intención de buscar más de una entrada. Además, puede agregar algunas filtraciones malas. Puede ser un verdadero escenario de perder-perder.

Limita las URL hasta el 5 o 12 que realmente te interesan y puedes hacer todo tipo de cargas de servicio y nunca notar el golpe.