Grape: Manejo de dependencias en Groovy
El manejo de dependencias generalmente es una pesadilla, incluso cuando usamos herramientas como Maven o Ivy. Hay que crear archivos de configuración y de construcción de proyectos, y eso ayuda mucho para proyectos complejos, pero cuando tenemos un script en Groovy, un programita rápido, pensamos que no queda otra opción que poner las dependencias en la línea de comandos.
Pero no es así.
Groovy incluye un excelente mecanismo para manejo de dependencias, llamado Grape, y es muy simple de usar. Supongamos que queremos hacer un script para conectarse a una base de datos y procesar algunos registros, y enviar al final un correo (ah y por supuesto dejar todo en un log con SLF4J).
Aquí pongo un ejemplo sencillo (sin lo del correo), usando la clase Sql de Groovy:
import groovy.sql.*
Logger log = LoggerFactory.getLogger(getClass())
log.info('Creando conexion a base de datos')
Sql sql = Sql.newInstance('jdbc:postgresql://localhost/javamexico', 'javamex', 'javamex2', 'org.postgresql.Driver')
log.info('Leyendo tabla')
sql.eachRow('SELECT * FROM usuario') {
println "Usuario $it.uname (${it.email}) registrado el dia $it.fecha_alta tiene reputacion $it.reputacion"
}
log.info('Listo.')
Si ejecutamos este script obtendremos un error:
$ groovy Ejemplo.groovy org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: /Users/ezamudio/Projects/Personal/ejemplos/Ejemplo.groovy: 4: unable to resolve class Logger @ line 4, column 8. Logger log = LoggerFactory.getLogger(getClass()) ^ 1 error
Necesitamos ejecutar el script indicando las dependencias en el classpath, algo como esto:
$ groovy -cp /usr/java/lib/postgresql-8.4-702.jdbc4.jar:/usr/java/lib/slf4j/slf4j-api-1.6.1.jar:/usr/java/lib/slf4j/slf4j-simple-1.6.1.jar Ejemplo.groovy 17 [main] INFO Ejemplo - Creando conexion a base de datos 245 [main] INFO Ejemplo - Leyendo tabla Usuario uname2 (b@c.com) registrado el dia 2009-12-22 09:59:23.825 tiene reputacion 48 Usuario domix (x@y.com) registrado el dia 2009-12-22 10:42:01.628 tiene reputacion 41 Usuario ezl (z@w.com) registrado el dia 2009-12-22 10:42:01.421 tiene reputacion 52 Usuario jb (x@g.c) registrado el dia 2009-12-22 10:42:01.477 tiene reputacion 79 Usuario uname1 (a@b.com) registrado el dia 2009-12-22 09:59:23.66 tiene reputacion 27 Usuario ecamacho (e@t.c) registrado el dia 2009-12-22 10:42:01.563 tiene reputacion 30 378 [main] INFO Ejemplo - Listo.
Bastante latoso, eso de poner el classpath en la línea de comando. Qué pasa si fue un script que hicimos al aventón, en un servidor remoto, mediante una sesión de ssh, por un enlace lento, y resulta que NO tenemos a la mano las bibliotecas necesarias? Nos falta un driver, o el logger simple de SLF4J...
Usando Grab
La manera más sencilla de usar Grab es por medio de anotaciones. Grab funciona de manera muy similar a Ivy o Maven, pero en tiempo de ejecución obtiene las dependencias necesarias. Si tienen instalado ya Groovy (y si no, qué esperan), pueden teclear en su línea de comando simplemente grab
para comprobar que tienen la herramienta. Con ella pueden instalar dependencias directamente en la línea de comando, y se guardan en un repositorio para el usuario que ejecuta el programa, de modo que quedará disponible para todos sus scripts en Groovy, siempre y cuando lo solicite por medio de la anotación @Grab
o usando directamente la clase Grape
. Así que al script agregamos esto:
import groovy.sql.*
@Grapes([
//Este va sin version, por lo tanto bajará el más nuevo
@Grab('postgresql:postgresql'),
//Este va con versión
@Grab('org.slf4j:slf4j-simple:1.6.1'),
//Esto es para que agregue las dependencias al classLoader del sistema
//Es necesario por el driver de PostgreSQL
@GrabConfig(systemClassLoader=true)
])
Logger log = LoggerFactory.getLogger(getClass())
...
//resto del script
Con lo anterior, al momento que ejecutamos el script, veremos que el script aparentemente tarda en arrancar... eso es porque se están descargando las dependencias que no se tienen instaladas aún y luego se ejecuta el script, sin necesidad de especificar las dependencias en la línea de comando.
Si solamente necesitan una dependencia, pueden poner directamente la anotación @Grab
sin tener que poner la de @Grapes
; esta última sirve para poder juntar varios @Grab
, opcionalmente con la de @GrabConfig
.
Como ya mencioné, se maneja un cache local. Para verlo, podemos ejecutar el comando grape
para ver qué tiene nuestro sistema (el mío se ve así porque ya lo he usado antes, pero borré algunas dependencias que vamos a usar):
$ grape list commons-logging commons-logging [1.1.1] org.apache apache [4] org.apache.commons commons-parent [5] org.slf4j slf4j-api [1.6.1] org.slf4j slf4j-parent [1.6.1] org.slf4j slf4j-simple [] org.springframework spring-asm [3.0.5.RELEASE] org.springframework spring-core [3.0.5.RELEASE] org.springframework spring-parent [3.0.5.RELEASE] postgresql postgresql [8.4-702.jdbc4] 10 Grape modules cached 9 Grape module versions cached
$ groovy Ejemplo.groovy 17 [main] INFO Ejemplo - Creando conexion a base de datos 245 [main] INFO Ejemplo - Leyendo tabla Usuario uname2 (b@c.com) registrado el dia 2009-12-22 09:59:23.825 tiene reputacion 48 Usuario domix (x@y.com) registrado el dia 2009-12-22 10:42:01.628 tiene reputacion 41 Usuario ezl (z@w.com) registrado el dia 2009-12-22 10:42:01.421 tiene reputacion 52 Usuario jb (x@g.c) registrado el dia 2009-12-22 10:42:01.477 tiene reputacion 79 Usuario uname1 (a@b.com) registrado el dia 2009-12-22 09:59:23.66 tiene reputacion 27 Usuario ecamacho (e@t.c) registrado el dia 2009-12-22 10:42:01.563 tiene reputacion 30 378 [main] INFO Ejemplo - Listo.
La siguiente vez que se ejecute el script, ya no se descarga nada porque se queda en el cache local y por tanto el script arrancará más rápido. Y después de ejecutarlo, si revisamos las dependencias instaladas en cache local:
$ grape list commons-logging commons-logging [1.1.1] org.apache apache [4] org.apache.commons commons-parent [5] org.slf4j slf4j-api [1.6.1] org.slf4j slf4j-parent [1.6.1] org.slf4j slf4j-simple [1.6.1] org.springframework spring-asm [3.0.5.RELEASE] org.springframework spring-core [3.0.5.RELEASE] org.springframework spring-parent [3.0.5.RELEASE] postgresql postgresql [8.2-504.jdbc3, 8.4-702.jdbc4, 9.0-801.jdbc4] 10 Grape modules cached 12 Grape module versions cached
Podemos ver que se reinstaló slf4j-simple, y dos versiones de los drivers de PostgreSQL.
Grape no solamente sirve para manejo de dependencias en scripts; también se pueden anotar clases, de modo que por ejemplo si tienen una clase que se encarga de enviar correos, y le ponen un @Grab
con Java Mail, entonces si usan esa clase en un script, se cargará la dependencia de Java Mail.
Dado que Grape usa internamente Ivy, también se hace resolución de dependencias transitivas. Es decir, si en su script indican que quieren usar la biblioteca A pero para poder usar esa biblioteca se necesita cargar B y C, y para poder usar B se necesita cargar la biblioteca D, entonces un simple @Grab('grupo:A')
causará que se carguen las bibliotecas A, B, C y D.
Limitaciones
Por el momento, el uso de @Grab
tiene algunas limitaciones, que afectan principalmente la integración con aplicaciones Java. Este ejemplo que puse aquí es un caso, de hecho. Si compilamos el script:
groovyc Ejemplo.groovy
Obtendremos un par de clases, Ejemplo.class
y Ejemplo$_run_closure1.class
. Si corremos esto desde Java, agregando al classpath las dependencias de Ivy y Groovy, obtendremos un error:
$ java -cp $GROOVY_HOME/embeddable/groovy-all-1.7.10.jar:$GROOVY_HOME/lib/ivy-2.2.0.jar:. Ejemplo Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: No suitable ClassLoader found for grab at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77) at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:198) at groovy.grape.GrapeIvy.chooseClassLoader(GrapeIvy.groovy:173) ... ...
Esto es un problema del cual el equipo de Groovy está consciente, espero que para la versión 1.8 lo arreglen. Será genial ver que una clase Groovy anotada con @Grab
se pueda agregar a una aplicación Java y que con eso, al ejecutar la aplicación se carguen las dependencias faltantes al classpath.
- ezamudio's blog
- Inicie sesión o regístrese para enviar comentarios
Comentarios
Muy bueno Grape
Es una gran herramienta, aunque no he visto como hacer que vaya notificando que librerias esta bajando y que porcentaje lleva. Esto me causo muchos problemas en un principio porque no sabia porque tardaba TANTO la ejecución de un script y como pense que no estaba ejecutandose bien pues mataba la ejecución del mismo y no dejaba que se bajaran todas las librerias correctamente.
log?
Si usas un sistema de log como log4j o slf4j, tal vez haya que configurar algo para org.apache.ivy (ponerle nivel DEBUG) y con eso sale lo que se está descargando...
No parece que tengan el log solucionado, todavía.
Viendo este tema de discusión, no parece que todavía esté solucionado:
http://groovy.329449.n5.nabble.com/Grape-log-output-when-using-annotatio...
pero al menos esta claro que lo tendrán en cuenta.
Ojalá en Java también se pudiera hacer algo así :)
Re: No parece que tengan el log solucionado, todavía.
Ese es el problema con Java, a Java no lo configuras con Java...Filosofía que manejan lenguajes cómo Groovy, Python o Ruby; hacer las configuraciones con el mismo lenguaje y no con el "auxilio" de un XML. Por ejemplo con Python, manejas el fichero __init__.py en donde toda la configuración (constantes de la aplicación) van aquí y son utilizables dentro del proyecto (cosa que con Maven o JPA por ejemplo, la configuración es vía XML).
En Java sería súper cómodo tener algo cómo Grape pero con Ivy o Maven. Sin XML para configurar y Java para programar, simplemente Java para configurar y Java para programar.
Vientos funciona muy bien :D !!
Pues ya no me quedé con la duda y lo probé:
Conecta.groovy
import groovy.grape.Grape
inicio()
@Grapes([
@Grab('mysql:mysql-connector-java:5.1.12'),
@GrabConfig(systemClassLoader=true)
])
def inicio(){
if(args){
def usuario=args[0]
println "bienvenido: ${usuario}"
Sql sql=Sql.newInstance("jdbc:mysql://localhost/agenda",usuario,"rooter","com.mysql.jdbc.Driver")
sql.eachRow("select * from usuarios") {
println "Usuarios de la base: ${it.nombre}"
}
}else{
println "error, debes introducir tu nombre"
}
}
Compilar y ver la lista
//ver la lista
grape list
Ejecutar y esperar el milagro
groovy Conecta.groovy
Problemas con java
Hola encantado de hablar contigo he visto que eres una máquina con java. Tengo un gran problema con java del cual no tengo ni idea. Bueno algo se, resulta que he notado que se estan conectanto a mi pc por medio de java (creo ven lo que hago) en una red local, pues es un internet compartido por mucha gente.
Hay un archivo java.policy que se supone que regula esto (o eso creo).
En fin es un problema grave de seguridad, pues ya me han estropeado varias veces mi email y demas.
Prodrias hecharme una mano y decirme que tendría que hacer para impedir conexiones java a mi pc desde otro pc (además he notado cosas raras en el navegador opera). Sin desintalar java, pues muchas webs lo usan.
Por favor ayudame estoy desesperado y no tengo ni idea de aumentar la seguridad.
Un saludo
????
?
tema
rastas, esas dudas nada tienen que ver con el tema de este post, por lo que deberías abrir un post aparte para esa duda.
Pero esa duda realmente tampoco tiene que ver con Java, según yo... no sé cómo determinaste que "se están conectando a tu pc por medio de Java" pero no entiendo bien qué quiere decir. De cualquiera manera es un tema aparte y probablemente no tenga nada que ver con Java, pero si abres un post nuevo donde expliques a detalle tu problema, ya todos podremos leerlo y probablemente sugerir soluciones.