Maven proyectos multimódulo
Crear un proyecto con Maven es relativamente fácil, una vez que lo inicias, te ajustas a su manera de organizar los directorios y todo son ventajas. Otra cosa útil es que podemos crear proyectos independientes que luego podemos unir/reutilizar en proyectos más complejos. Lo que vamos a hacer en este ejemplo es crear tres proyectos, y uno de ellos, el main o principal, hará uso de los otros dos. No tiene que ver con proyectos padre-hijo y herencia, aquí se trata de proyectos totalmente independientes. De paso revisaremos algunos goals útiles de Maven
Los proyectosVamos a crear tres proyectos, main, inputoutput y utils. No hacen gran cosa. Para crearlos vamos a usar el goal archetype:create con los parámetros donde indicamos los indentificadores de proyecto. Si no pusieramos esos parámetros los podriamos meter a posteriori en el pom.xml
linux:~/java/maven/multimodule# mvn archetype:create -DgroupId=info.pello.maven.inputoutput -DartifactId=inputoutput -DpackageName=info.pello.maven [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:create] (aggregator-style) [INFO] ------------------------------------------------------------------------ Downloading: http://repo1.maven.org/maven2/org/apache/maven/wagon/wagon-file/1.0/wagon-file-1.0.pom [WARNING] Unable to get resource 'org.apache.maven.wagon:wagon-file:pom:1.0' from repository central (http://repo1.maven.org/maven2): Error transferring file: repo1.maven.org [INFO] [archetype:create {execution: default-cli}] [WARNING] This goal is deprecated. Please use mvn archetype:generate instead [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central [WARNING] repository metadata for: 'artifact org.apache.maven.archetypes:maven-archetype-quickstart' could not be retrieved from repository: central due to an error: Error transferring file: repo1.maven.org [INFO] Repository 'central' will be blacklisted [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:RELEASE [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: info.pello.maven.inputoutput [INFO] Parameter: packageName, Value: info.pello.maven [INFO] Parameter: package, Value: info.pello.maven [INFO] Parameter: artifactId, Value: inputoutput [INFO] Parameter: basedir, Value: /root/java/maven/multimodule [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] project created from Old (1.x) Archetype in dir: /root/java/maven/multimodule/inputoutput [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 6 seconds [INFO] Finished at: Fri Aug 02 15:00:20 CEST 2013 [INFO] Final Memory: 10M/25M [INFO] ------------------------------------------------------------------------ linux:~/java/maven/multimodule#(archetype:generate se me estaba quedando colgado...)
Con los otros podemos hacemos lo mismo, aunque el main habrá que hacerlo después de los demás:
mvn archetype:create -DgroupId=info.pello.maven.utils -DartifactId=utils -DpackageName=info.pello.maven ... mvn archetype:create -DgroupId=info.pello.maven.main -DartifactId=main -DpackageName=info.pello.maven ...Los programas
Este es el aspecto que tiene la clase InputOutput del módulo inputoutput que será utilizada por el módulo main:
package info.pello.maven.inputoutput; import java.util.Scanner; import java.io.InputStream; /** * input/output class to make console life easier * @author Pello Xabier Altadill Izura * @greetz to Zizur */ public class InputOutput { Scanner reader; /** * default constructor */ public InputOutput () { } /** * constructor with parameter * @param inputStream */ public InputOutput (InputStream inputStream) { this.reader = new Scanner(inputStream); } /** * reads a String from console * @return String readed */ public String read () { return reader.nextLine(); } /** * reads int from console * @return int readed */ public int readInt () { return reader.nextInt(); } /** * writes a String to output * @param String to be printed */ public void write (String msg) { System.out.println(msg); } }
Y este sería el aspecto del método main del módulo principal. Está importanto clases de otros módulos.
package info.pello.maven.main; import info.pello.maven.inputoutput.InputOutput; /** * main class to test Maven multimodule project * @author Pello Xabier Altadill Izura * @greetz now 4 u */ public class Main { // main class public static void main (String [] args) { InputOutput inputOutput = new InputOutput(System.in); System.out.println("Does it work, really?"); inputOutput.write("Using input output lib"); } }
Si ya hemos hecho mvn install en los otros módulos ahora lo hacemos en este
ejecutando con exec:javaMediante el plugin exec y el goal java podemos lanzar la ejecución de nuestro programa main, dentro de la carpeta del proyecto main hacemos:
linux:~/java/maven/multimodule/main# mvn exec:java -Dexec.mainClass=info.pello.maven.main.Main [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'exec'. [INFO] ------------------------------------------------------------------------ [INFO] Building main [INFO] task-segment: [exec:java] [INFO] ------------------------------------------------------------------------ [INFO] Preparing exec:java [INFO] No goals needed for project - skipping [INFO] [exec:java {execution: default-cli}] Does it work, really? Using input output lib [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3 seconds [INFO] Finished at: Fri Aug 02 17:11:56 CEST 2013 [INFO] Final Memory: 7M/18M [INFO] ------------------------------------------------------------------------ linux:~/java/maven/multimodule/main#
Vale funciona pero ¿Qué pasa si ejecutamos ese fichero jar lejos de ese contexto y sin Maven?
linux:/tmp# java -cp supermain.jar info.pello.maven.main.Main Exception in thread "main" java.lang.NoClassDefFoundError: info/pello/maven/inputoutput/InputOutput at info.pello.maven.main.Main.main(Main.java:14) Caused by: java.lang.ClassNotFoundException: info.pello.maven.inputoutput.InputOutput at java.net.URLClassLoader$1.run(URLClassLoader.java:217) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:205) at java.lang.ClassLoader.loadClass(ClassLoader.java:321) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294) at java.lang.ClassLoader.loadClass(ClassLoader.java:266) ... 1 more linux:/tmp#
Casca sin remedio. Maven se encargaba de las dependencias pero ese jar no nos sirve fuera de su entorno. ¿Cómo hacer para que el jar contenga todo lo necesario?
El plugin assemblyMetiendo este plugin en nuestro proyecto Maven (habrá que meter una entrada en los plugins del fichero pom.xml) ya podremos generar un jar que contiene todo.
mvn install assembly:assembly
Ese comando va a generar dentro de target un fichero llamado supermain-with-jar-dependencies.jar que ya podremos ejecutar directamente:
linux:/tmp# java -cp supermain-jar-with-dependencies.jar info.pello.maven.main.Main Does it work, really? Using input output lib linux:/tmp#
Otra cosa que podemos hacer es meter mano en el ciclo package para que siempre incluya todas los ficheros necesarios para hacer un jar independiente.
El pom.xml finalHe metido algunas etiquetas de info, de licencia, de desarrolladores, simplemente para mostrar más cosas que se pueden hacer. Ojo a la sección plugins, ahí es donde hemos metido el plugin de assembly y hemos modificado el proceso de package. Los pom.xml de los otros módulos no tienen nada en especial.
Otros comandos útiles4.0.0 info.pello.maven.main main 1.0-SNAPSHOT jar main http://www.pello.info GPL v3 http://www.gnu.org/licenses/gpl/gpl3.txt repo The talibans license of choice Pello.info Corp. http://www.pello.info pelloxabier Pello Xabier Altadill Izura pello@falsedomain.org http://www.pello.info Pello.info Corp. http://www.pello.info developer coffee dealer +1 UTF-8 supermain maven-assembly-plugin jar-with-dependencies junit junit 3.8.1 test info.pello.maven.inputoutput inputoutput 1.0-SNAPSHOT info.pello.maven.utils utils 1.0-SNAPSHOT
install: el ciclo de vida completo.
Si ejecutamos
mvn install
Pondremos en marcha un lifecycle o ciclo de cida, que en realidad se componen de varios goals de maven. Este en concreto lleva a cabo entre otros:
- resources:resource: copia los recursos desde el src al target
- compiler:compile: compila el código.
- resources:testResources: copia los recursos utilizados en los test al target
- compiler:testCompile: compila los test unitarios
- surefire:test: ejecuta los test unitarios
- jar:jar: ejecuta a jar-jar binks. Broma. Genera el jar.
Hay más fases que esas, por ejemplo la última es install:install y esa es importante ya que toma el fichero jar generado y lo guarda en nuestro repositorio Maven. De esa forma podremos hacer uso de ese jar desde otros proyectos Maven. ¿Y cómo lo localizaremos? mediante las coordenadas!! es decir mediante el grupo, artifactId, etc...
Si echamos un ojo en el repositorio, en mi caso en linux en mi directorio home tendré una carpeta oculta .m2, podré encontrar el proyecto
linux# ls /root/.m2/repository/info/pello/maven/inputoutput/inputoutput/1.0-SNAPSHOT/ inputoutput-1.0-SNAPSHOT.jar maven-metadata-local.xml inputoutput-1.0-SNAPSHOT.pom linux#Otros ciclos, o ciclos parciales
Si no queremos hacer todo el install pero sí parte de él, en lugar de invocar los goal por separada tenemos ciclos reducidos. También hay ciclos que hacen otras cosas
- site: genera los docs
- test: pasa los test y genera informes de los testeos
- package: empaqueta el programa (jar)
Una de las mejores cosas que tiene Maven sin duda es la resolución de dependencias, cosa que ya he podido comprobar en los proyectos Spring/Maven. Cada vez que necesitaba alguna librería, bastaba con sacar la información de esa librería e incluirla en la sección dependencies del fichero pom.xml de Maven. ¿De dónde las saco? Se puede ir a un repositorio Maven (oficial: http://maven.org/, otros: http://mvnrepository.com) y buscar el que necesitemos. Nos proveerán con el XML necesario para incluir la dependencia. Una vez metida Maven se encarga de bajar el jar que necesitamos y lo mete en el repositorio para futuros usos también.
Si nuestro proyecto va acumulando dependencias podemos usar estos comandos para verlas:
mvn dependency: resolve
Y este para ver en forma de arbol
mvn dependency:treeAyuda
Cuando nos hemos perdido en una ciudad o en la carretera nunca hay que preguntar porque es muy poco varonil. Pero en la consola, donde nadie nos ve, lo podemos hacer sin que nadie ponga en cuestión nuestra virilidad. Si te has tomado en serio esto último, deja de leer y busca la ironía y el sentido del humor allá donde lo perdiste. A lo que iba, antes de que te vayas corriendo a stackoverflow puedes probar la ayuda de Maven, que oh cielos, no son sino goals del plugin help
mvn help:help
Si ejecutamos eso el plugin de help nos presenta los 9 goals de los que disponemos. El que nos cuenta cosas interesantes es help:describe. Ejemplos:
- mvn help:describe -Dcmd=install: ayuda sobre un lifecycle
- mvn help:describe -Dcmd=exec:java: ayuda sobre un goal de exec
- mvn help:describe -Dplugin=org.apache.maven.plugins:maven-help-plugin: ayuda sobre una plugin
Si le metemos el parámetro -Ddetail=true
la ayuda es mucho más verbosa