[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Tareas Ant empaquetadas con One-Jar



Hola,

Recientemente he estado tratando de encontrar una solución a un problema que 
me parece interesante comentar.

El problema en cuestión tiene que ver con cómo empaquetar tareas Ant de forma 
que autoincluyan sus dependencias, y así no obligar a los proyectos que las 
usen a conocerlas y declararlas.

Existe una librería, llamada one-jar [1], que permite encapsular en un solo 
jar tanto las clases de la aplicación como sus módulos, de forma que se pueda 
ejecutar sin necesidad de definir el classpath, con 

java -jar nombre-del-jar

Este mismo principio es el que perseguía, pero no para aplicaciones de linea 
de comando sino tareas Ant. En lugar de llamar a un main(), sería suficiente 
instanciar una clase y dejar que Ant llame a su método execute(). Además, 
para hacerlo genérico, mi intención era crear una sola clase proxy, que fuera 
capaz de leer del manifest la tarea Ant real, y la instanciara dentro de un 
classloader que fuera capaz de ver las dependencias de dentro del propio jar.

Al ponerme con ello, me di cuenta de que no podía crear un proxy estándar, 
dado que Ant instancia las clases por reflexión, y al obtiener una referencia 
a una clase Task directamente, no puedo envolverla de ninguna forma.
La necesidad de usar un proxy se debe a que la idea era que esa clase fuera 
capaz de instanciar cualquier tarea Ant, independientemente de sus atributos 
o elementos. Y no poder envolverla hacía a priori imposible que Ant pudiera 
encontrar los mismos métodos en la clase proxy que en la original (esos 
métodos los va llamando Ant en función del contenido del build.xml).
La única opción viable sería utilizar BCEL [2] para añadir métodos 
dinámicamente a la clase proxy conforme a la API pública de la tarea Ant 
original, y cuyo código fuera simplemente llamadas a los métodos homónimos 
originales.

Una vez averiguo cómo generar estos métodos con BCEL (con la ayuda de la clase 
BCELifier), compruebo de que Ant no los encuentra. Investigo más y saco la 
conclusión de que el resultado de mis modificaciones es simplemente el 
bytecode de una clase que se llama igual que el proxy original, pero que no 
lo reemplaza. Para sustituir uno por otro, en algún foro comentan que hay que 
recurrir a una solución poco elegante: llamar al método 
ClassLoader.defineClass(String name, byte[] bytecode, ...), con el nuevo 
bytecode. El problema es que ese método está protegido, así que hay que tirar 
de AccessibleObject.setAccessible(true).
Acepto a regañadientes el precio a pagar, y al probarlo compruebo que 
lamentablemente no funciona: el ClassLoader dice que la clase ya está 
cargada, y lo hace desde dentro de un método nativo.

Así que la conclusión que saco ahora mismo es que se puede cambiar el bytecode 
en caliente, siempre que las clases no hayan sido ya leídas y cargadas. En mi 
caso, el código que trato de cambiar es el propio código que se está 
ejecutando (si bien no el mismo método).
Podría investigar más y analizar si en realidad se puede hacer utilizando 
javaassist o aspectj, bien para utilizarlos, o bien para emular el 
procedimiento que utilizan.

Otra opción sería estudiar si el classloader de Ant permitiría devolver el 
bytecode alternativo a pesar de que su clase padre vea el original.

Temporalmente, he decidido sacrificar la flexibilidad, definir los setters 
explícitamente para la tarea Ant con la que estoy trabajando, y ver si al 
menos el mecanismo de carga de clases de One-Jar funciona correctamente 
dentro de Ant.

Este es un ejemplo de un proyecto cuyas dificultades difícilmente se pueden 
prever de antemano, y por tanto estimar. Pero es interesante y estimulante si 
no tienes (o te creas) presión por encontrar una solución aceptable.

Un saludo,
Jose.

[1] http://one-jar.sf.net
[2] http://jakarta.apache.org/bcel

Attachment: signature.asc
Description: This is a digitally signed message part.