En la entrada de hoy vamos a tratar un problema un poco más complejo, y por lo tanto la solución requiere también un poco más de trabajo, pero el resultado final merece la pena.
En este caso el enunciado del problema sería algo tal que así:
- Supongamos un proceso que puede tener varias implementaciones diferentes y queremos permitir añadir diferentes versiones y permitir escoger una implementación simplemente a través de una cadena de configuración.
La solución que proponemos usa los mecanismos proporcionados en el API básico de Java (se necesita Java 6), sin acudir a soluciones más complejas como OsGI. En caso de necesitar poder actualizar/cambiar las dependencias en tiempo de ejecución y/o tener que poder permitir varias versiones de la misma dependencia a la vez en una misma JVM, sí deberíamos usar algo más complejo, pero si no es necesario, quizá no nos merezca la pena entrar en soluciones tan complejas.
Volviendo a nuestra solución propuesta, la idea es utilizar la clase
java.util.ServiceLoader que nos permite declarar y recorrer las clases que implementan una Interfaz dada, pudiendo añadir nuevas implementaciones en el classpath simplemente empaquetando las clases adecuadamente.
El primer punto a tener en cuenta es que podríamos cargar con el ServiceLoader directamente las implementaciones del proceso, sin embargo esta estrategia presenta un problema. ServiceLoader instancia las clases al recorrerlas buscando las implementaciones posibles, así que tenemos que tener toda las dependencias de todas las implementaciones posibles en el classpath o tendremos problemas. En algunos casos eso puede no ser problemático, pero imaginemos que tenemos varias soluciones y que unas usan Hibernate, otras JPA, otras JDO... al cargarlas todas tendríamos que tener todas las dependencias en el classpath, aunque sólo fuéramos a usar JDO, por ejemplo.
Así pues, lo que haremos será que la clase cargada a través de
ServiceLoader sea únicamente una utilidad que nos diga el nombre (una cadena) de clase que realmente implemente el proceso. De esta forma podemos cargar la clase utilidad tranquilamente en el classpath sin necesitar cargar todas las dependencias de la implementación.
En la implementación necesitaremos:
- La interfaz a implementar por todas las clases utilidad. Para hacerlo más flexible permitiremos que una sola clase de utilidad pueda especificar varias implementaciones, cada una con su “clave”:
package my.pckg;
public interface ProcessImplementationRegistration
{
/*
Las implementaciones han de devolver una lista de pares clave/implementacion del proceso.
Por ejemplo: {{“jpa”,”com.my.JPAImplementation”},
{“jdo”,”com.my.JDOImplementation”}}
*/
public String[][] getProcessImplementations();
}
- En la clase que va a utilizar las implementaciones, cargar las clases que utilizando el mecanismo de la clase java.util.ServiceLoader, y registrar las implementaciones según su clave:
ServiceLoader theServiceLoader = ServiceLoader.load(ProcessImplementationRegistration.class);
for(ProcessImplementationRegistration pirInstance: theServiceLoader)
{
// Usar pirInstance.getProcessImplementations() para registrar
// las implementaciones por clave
...
}
- Una vez hecho esto, para añadir una implementación simplemente tenemos que crear la clase que implemente la interfaz my.pckg.ProcessImplementationRegistration, empaquetarla en un jar y añadir en el directorio META-INF del jar, un fichero llamado my.pckg.ProcessImplementationRegistration donde incluiremos el nombre de la clase que implemente la interfaz.
Por ejemplo, clase que implementará la interfaz será:
package com.my;
public class MyJPARegistration implements ProcessImplementationRegistration
{
/*
Devolvemos la implementacion que registra este .jar
*/
@Override
public String[][] getProcessImplementations()
{
return new String[][]{{“jpa”,”com.my.JPAImplementation”}}
}
}
El fichero META-INF/my.pckg.ProcessImplementationRegistration contendrá únicamente la linea:
com.my.MyJPARegistration
y obviamente en el .jar incluiremos también la clase com.my.JPAImplementation y sus clases auxiliares necesarias si las hubiera.
Colocamos el .jar en el classpath, reiniciamos la maquina virtual Java y voilà: nuestra implementación será descubierta y clasificada según la clave “jpa”. Además, si no la usamos no hará falta que tengamos las clases de JPA en el classpath, por lo que podemos tener los .jar de registro de todas nuestras opciones y sólo necesitaremos tener en el classpath las dependencias de las que realmente usemos.
Este es un mecanismo que utilizan algunos frameworks para incluir plugins opcionales de forma sencilla, basta incluir el fichero .jar en el classpath, sin necesitar incluir las dependencias a no ser que realmente los utilicemos.
Eso es todo. No es una implementación completa pero espero haberos provisto de todos los detalles para que podáis experimentar vosotros mismos. Sólo hace falta tener Java 6, así que animaros a hacer alguna prueba.
Un saludo y hasta la próxima.