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 proyectos

Vamos 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:java

Mediante 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 assembly

Metiendo 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 final

He 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.


  4.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
    
  


Otros comandos útiles
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)
Dependencias

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:tree
Ayuda

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