Objetivos
El objetivo de este artículo es servir de guía básica para la migración manual de proyectos Java no Maven a entorno Maven. Pensado y dirigido especialmente a personas con poca experiencia previa en Maven, pero con algún conocimiento previo.
Preparación del proyecto
Como es obvio, para migrar un proyecto Java a Maven, deberemos disponer de sus fuentes y todas las librerías dependientes.
Ya que vamos a realizar modificaciones estructurales importantes en el proyecto, conviene asegurarnos de que las fuentes no están conectadas a ningún sistema de control de versiones como Subversión. Para realizar esto, podemos o bien eliminar manualmente todos los directorios .svn de nuestro proyecto o bien desde Eclipse desconectar el proyecto.
Si lo vamos a realizar de forma manual, debemos asegurarnos de eliminar todos los directorios .svn (o .CVS si el proyecto a migrar estuviese conectado a un CVS) A modo de ejemplo, en Linux podemos ejecutar el siguiente comando desde el directorio del proyecto para eliminar todas las referencias a carpetas ‘.svn’:
rm -rf `find . -type d -name .svn`
Será mucho más sencillo hacerlo de forma automática desde Eclipse. Para desconectar el proyecto, teniéndolo seleccionado pulsaremos sobre el botón derecho del ratón: Team -> Disconnect.
Adaptación del proyecto
Aunque la estructura por defecto Maven puede ser modificada, es muy recomendable adaptar la organización del proyecto a migrar a la estructura por defecto más generalizada:
Para hacer esta modificación de la estructura hay que tener especial cuidado en algunos aspectos o convenciones Maven:
- Los ficheros de recursos: Maven hace una distinción entre los ficheros de clases java y los ficheros de recursos .properties y XML y, de hecho, salvo que le digamos lo contrario, no hará ningún tratamiento de los ficheros no java que se encuentren en el directorio src/main/java. Por este motivo, tendremos que localizar todos los ficheros de recursos (properties, XML) que se encuentran en el directorio java y moverlos al directorio resources (src/main/resources).
- Eliminar la carpeta classes. Es muy común que proyectos no Maven tengan en su carpeta WEB-INF/classes los compilados del proyecto. Esta situación no solo no es recomendable, sino que puede producir problemas, por lo que los eliminaremos confiando en Maven para su generación cuando sea necesario.
Definir el pom.xml del proyecto
Una vez que la estructura del proyecto se ha adaptado a la filosofía Maven, el siguiente paso es definir el pom.xml del proyecto.
Partiremos de un fichero pom.xml básico. Este fichero de origen no tiene dependencias; durante el proceso lo adaptaremos y posteriormente iremos refinando la configuración hasta alcanzar una «mavenización» completa del proyecto.
Una vez colocado el pom.xml en la raíz de la aplicación, lo modificamos indicando el nombre del proyecto y la información básica relativa a los desarrolladores, organización y repositorio de dependencias utilizado.
Antes de continuar y añadir las dependencias, podemos hacer una comprobación previa y verificar que efectivamente nuestro IDE, en este caso Eclipse, reconoce correctamente la nueva estructura del proyecto.
Para ello nos vamos a la consola y en el directorio en el que se encuentra nuestro proyecto ejecutamos la siguiente orden:
mvn clean eclipse:eclipse
(Requiere disponer de Maven correctamente instalado en la máquina).
Si la configuración del entorno es correcta, obtendremos una salida similar a esta:
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ------------------------------------------------------------------------
[INFO] Building ejemplo
[INFO] task-segment: [clean, eclipse:eclipse]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting file set: /home/felix/workspaces/ejemplo/target (included: [**], excluded: [])
[INFO] Preparing eclipse:eclipse
[INFO] No goals needed for project - skipping
[INFO] [eclipse:eclipse]
[INFO] Adding support for WTP version 1.5.
[INFO] Using source status cache: /home/felix/workspaces/ejemplo/target/mvn-eclipse-cache.properties
[INFO] File /home/felix/workspaces/ejemplo/.project already exists.
Additional settings will be preserved, run mvn eclipse:clean if you want old settings to be removed.
[INFO] Wrote Eclipse project for "ejemplo" to /home/felix/workspaces/ejemplo.
[INFO]
Sources for some artifacts are not available.
Please run the same goal with the -DdownloadSources=true parameter in order to check remote repositories for sources.
List of artifacts without a source archive:
o javax.servlet:servlet-api:2.4
o javax.servlet.jsp:jsp-api:2.1
o junit:junit:3.8.1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Wed Sep 3 11:54:28 CET 2008
[INFO] Final Memory: 10M/80M
[INFO] ------------------------------------------------------------------------
Nos vamos a Eclipse, refrescamos el proyecto (F5 teniendo seleccionado el proyecto) y comprobamos que la nueva estructura es correcta.
Ahora que hemos comprobado que eclipse ha reconocido la nueva estructura, el siguiente paso es definir en nuestro pom.xml las dependencias. Para hacer esto eliminamos todos los .jar que se encuentran en el directorio /WEB-INF/lib y nos quedamos con una copia de ellos para posteriormente ir resolviendo las dependencias del proyecto original. Una recomendación es cortar la carpeta /WEB-INF/lib a otra ruta de nuestro disco.
Resolución de dependencias
Esta es la fase más delicada del proceso, ya que debemos asegurarnos de que todas las dependencias del proyecto quedan correctamente declaradas en el fichero pom.xml. Para realizar esta tarea es recomendadle utilizar alguna aplicación web que nos ayude a la localización de las librerías, como por ejemplo MvnRepository o MvnBrowser (recomendada), en la que podremos buscar cada uno de nuestros jar y encontrar la definición en el pom.xml que debemos utilizar.
Gracias a esta web, una vez que tengamos localizada la dependencia, solo tendremos que copiar el bloque POM Dependency a nuestro fichero pom.xml.
Iremos repitiendo este proceso de forma iterativa para todos nuestras librerías .jar y añadiendo la definición de la dependencia a nuestra zona de dependencies, con ciertas salvedades y consideraciones que introducimos a continuación.
Consideraciones especiales
Hay ocasiones en las que determinar la definición de la dependencia en nuestro pom.xml puede no ser una tarea trivial. Se comentan a continuación los mecanismos de actuación recomendados según diversas casuísticas:
No se indica la versión de la librería en el nombre del fichero.
Por ejemplo, podemos encontrarnos una librería llamada axis-ant.jar, por lo que tendremos que averiguar previamente la versión de la librería de cara a definir la dependencia en el fichero pom.xml. Para esto suele ser muy efectivo descomprimir la librería y consultar el fichero /META-INF/MANIFEST.MF. A modo de ejemplo, en dicha librería mencionada observaríamos una linea similar a la siguiente: «Implementation-Version: 1.2.1 2243 June 14 2005». Una vez que conozcamos la versión podremos realizar la búsqueda de la forma usual. , 1.1.1.1. Sabemos que la librería fue generada por maven, pero no conocemos su definición.
No conocemos el groupId, artifactId, etc. Podemos descomprimir el .jar y acceder al fichero pom.xml que se encuentra en /META-INF/maven para comprobar el groupId, artifactId y versión de la dependencia.
Dependencias especiales JEE.
Algunos jars, como por ejemplo servlet.jar, son aportados por el contenedor de aplicaciones, por lo que tendremos que definirlas marcándolas como provided. Esto le indica a Maven que estas librerías pueden ser utilizadas en tiempo de compilación, pero no serán agregadas al empaquetado final. Para declarar una dependencia de esta forma incorporaremos en nuestro pom.xml un código como este:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
Librerías ya consideradas en las dependencias transitivas
Muchas de las dependencias dependen a su vez de otras librerías, por lo que Maven añadirá de forma transitiva nuevas librerías a nuestro proyecto. En estos casos no necesitamos definir estas nuevas dependencias transitivas en nuestro pom.xml. Como ejemplo, supongamos que un proyecto depende de estas librerías:
- xalan-2-6.0.jar
- xercesImpl
- xml-apis
Al buscar la librería xalan-2-6.0.jar, observamos que la librería depende a su vez de las otras dos librerías. Por ello, para este ejemplo bastaría con declarar la dependencia a xalan-2-6.0.jar en nuestro pom.xml.
Conflictos entre librerías
En ocasiones nos podemos encontrar con que dos librerías tengan en sus dependencias a una misma tercera librería, pero de distinta versión. Ello haría que maven incluyese en nuestro Classpath 2 versiones de una misma librería, originando conflictos por duplicado de clases. En ese caso debe declararse en la dependencia de una de ellas una directiva para excluir la descarga de la librería conflictiva.
Por ejemplo, en un proyecto deseamos utilizar la librería siguiente:
<artifactId>jaxws-api</artifactId>
<groupId>javax.xml.ws</groupId>
<version>2.1-1</version>
Y tenemos dependencia a com.sun.xml.ws / jaxws-rt, esta librería se trae como dependencia transitiva otra versión de la librería mencionada anteriormente. Declaramos una exclusión para evitarlo:
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.4</version>
<exclusions>
<exclusion>
<artifactId>jaxws-api</artifactId>
<groupId>javax.xml.ws</groupId>
</exclusion>
</exclusions>
</dependency>
Tenemos una librería que se encuentra en los repositorios, pero no hemos podido averiguar el número de versión.
En esta situación, si la librería no parece problemática, podríamos optar por elegir la última versión de la dependencia. En el caso de que consideremos que la versión que utiliza el proyecto puede originar problemas, lo mejor es añadir esta librería manualmente a nuestro repositorio (como si fuese una librería específica del proyecto), según se explica en el próximo punto.
La librería es específica del proyecto.
En estos casos tendremos que subir la dependencia a nuestro repositorio de librerías y, para evitar conflictos posteriores, se recomienda que sea subida con el mismo groupId de nuestro proyecto. Supongamos que migrando un proyecto nos topamos con librería example1.jar que no se encuentra en ningún repositorio público. En este caso la subimos a nuestro repositorio por ejemplo con la siguiente definición:
<dependency>
<groupId>com.viavansi.examples</groupId>
<artifactId>example1</artifactId>
<version>0.0.1</version>
</dependency>
En la siguiente figura vemos cómo desplegamos una librería a Artifactory:
Comprobación de dependencias
Una vez que hayamos finalizado este proceso de definición de todas las dependencias en nuestro pom.xml, ejecutaremos en consola mvn dependency:tree (situados en el directorio donde se encuentre el fichero pom.xml del proyecto) para comprobar que las dependencias que se han incorporado al proyecto son las correctas. Este paso de verificación es muy importante en las migraciones de este tipo de proyectos, ya que es muy posible que alguna dependencia transitiva genere problemas. Como ejemplo, podemos observar la siguiente salida en la que comprobamos que no hay dependencias transitivas que entren en conflicto con alguna de nuestras librerías:
[INFO] [dependency:tree]
[INFO] com.viavansi.examples.examples1:war:0.0.1
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- javax.servlet.jsp:jsp-api:jar:2.1:provided
[INFO] +- javax.servlet:servlet-api:jar:2.4:provided (scope not updated to compile)
[INFO] +- javax.activation:activation:jar:1.1:compile
[INFO] +- avalon-framework:avalon-framework-impl:jar:4.1.5:compile
[INFO] +- axis:axis-ant:jar:1.2.1:compile
[INFO] +- axis:axis:jar:1.2.1:compile
[INFO] +- javax.mail:mail:jar:1.3.1:compile
[INFO] +- commons-beanutils:commons-beanutils:jar:1.6:compile
[INFO] +- commons-codec:commons-codec:jar:1.3:compile
[INFO] +- commons-collections:commons-collections:jar:3.2:compile
[INFO] +- commons-dbcp:commons-dbcp:jar:1.2.2:compile
[INFO] +- commons-digester:commons-digester:jar:1.6:compile
[INFO] | - xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] +- commons-discovery:commons-discovery:jar:0.2:compile
[INFO] +- commons-httpclient:commons-httpclient:jar:3.1:compile
[INFO] +- commons-io:commons-io:jar:1.3.1:compile
[INFO] +- commons-lang:commons-lang:jar:2.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1:compile
[INFO] | +- logkit:logkit:jar:1.0.1:compile
[INFO] | - avalon-framework:avalon-framework:jar:4.1.3:compile
[INFO] +- commons-pool:commons-pool:jar:1.3:compile
[INFO] +- displaytag:displaytag:jar:1.1:compile
[INFO] +- displaytag:displaytag-export-poi:jar:1.1:compile
[INFO] +- dom4j:dom4j:jar:1.6.1:compile
[INFO] +- org.extremecomponents:extremecomponents:jar:1.0.1:compile
[INFO] +- com.lowagie:itext:jar:1.4.8:compile
[INFO] +- oro:oro:jar:2.0.7:compile
[INFO] +- javax.xml:jaxrpc-api:jar:1.1:compile
[INFO] +- javax.servlet:jstl:jar:1.1.2:compile
[INFO] +- log4j:log4j:jar:1.2.8:compile
[INFO] +- com.oracle:ojdbc14:jar:9.0.2.0.0:compile
[INFO] +- poi:poi:jar:2.5.1-final-20040804:compile
[INFO] +- javax.xml.soap:saaj-api:jar:1.2:compile
[INFO] +- org.springframework:spring:jar:2.0.5:compile
[INFO] +- taglibs:standard:jar:1.1.2:compile
[INFO] +- struts:struts:jar:1.2.9:compile
[INFO] | +- commons-fileupload:commons-fileupload:jar:1.0:compile
[INFO] | +- commons-validator:commons-validator:jar:1.1.4:compile
[INFO] | - antlr:antlr:jar:2.7.2:compile
[INFO] +- wsdl4j:wsdl4j:jar:1.5.1:compile
[INFO] +- wss4j:wss4j:jar:1.5.0:compile
[INFO] +- xalan:xalan:jar:2.6.0:compile
[INFO] +- org.apache.xmlgraphics:xmlgraphics-commons:jar:1.2:compile
[INFO] +- xml-security:xmlsec:jar:1.2.1:compile
[INFO] +- org.directwebremoting:dwr:jar:2.0.3:compile
[INFO] +- com.viavansi:plantilla-client:jar:0.0.13:compile
[INFO] +- net.sourceforge.barbecue:barbecue:jar:1.5-beta1:compile
En caso de detectar librerías duplicadas a causa de las dependencias transitivas, deberemos proceder a definir las exclusiones necesarias en las dependencias afectadas, según se indicó anteriormente.