Maven. Gestión de dependencias
En el desarrollo de un proyecto es casi seguro que tendremos que hacer uso de componentes software que nos ofrezcan funcionalidades ya desarrolladas, tanto por terceras partes como por nosotros mismos. En el fichero pom del proyecto se indican las dependencias a añadir dentro de la etiqueta <dependencies> de la siguiente forma:
<project>
...
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
...
</project> Para cada dependencia a utilizar debemos especificar sus datos identificativos: groupId artifactId y version. Opcionalmente podemos definir el ámbito de aplicación mediante la etiqueta <scope>, que puede tomar uno de los siguientes valores:
-
compile. Es el valor por defecto utilizado en caso de no especificarse ningún scope. Las dependencias añadidas con este valor están disponibles en todos los classpath del proyecto.
-
provided. Se utiliza cuando se espera que la dependencia sea ofrecida en tiempo de ejecución por un JDK o un contenedor, por lo que solo está disponible a la hora de compilar y ejecutar los tests. Un ejemplo es el uso en una aplicación web del API Servlet. Es necesario tenerlo disponible a la hora de compilar un servlet pero no es necesario incluido en el paquete war final ya que esta funcionalidad la ofrece el contenedor de servlets del servidor de aplicaciones.
-
runtime. Son dependencias que se requiere que estén disponibles en tiempo de ejecución y a la hora de ejecutar los tests pero no es necesario disponer de ellas a la hora de compilar.
-
test. Son dependencias que solo están disponibles para la ejecución de los tests.
-
system. Similar a provided, con la difrencia de que hay que especificar la ruta del paquete jar mediante
<systemPath>ruta/del/jar</systemPath>. Este valor es obsoleto y se desaconseja su uso ya que dificulta la configuración del proyecto al moverlo entre máquinas diferentes. -
import. Para usarla hay que especificar que la dependencia es de tipo pom,
<type>pom</type>. En este caso, la dependencia es reemplazada con todas las dependencias declaradas en el pom.
Dependencias transitivas
Cuando añadimos una dependencia a un proyecto es posible que la misma dependa a su vez de otros componentes, los cuales pueden tener sus propias dependencias y así sucesivamente. Maven evita tener que declarar explícitamente todas éstas dependencias transitivas ya que indicando la dependencia requerida se encargará de incluir en el proyecto todos los paquetes necesarios de forma automática.
Puede darse el caso que en el árbol de dependencias del proyecto tengamos un mismo artefacto incluido varias veces pero con diferentes versiones. En este caso, Maven incluirá en el proyecto el artefacto que se encuentre más cerca en el árbol de dependencias. Por ejemplo, si tuvieramos el siguiente caso:

se incluiría la versión 1.0 del artefacto D ya que es la ruta más corta entre A y D (A-E-D frente a A-B-C-D). Si quisiéramos utilizar en A la versión 2.0 tendríamos que añadir ésta dependencia directamente, de forma que no se incluyera en el proyecto de forma transitiva. Por otro lado, es una buena práctica incluir específicamente las dependencias que se usan directemente. Ofrece una documentación directa del proyecto y evita problemas en caso de cambios inesperados en el árbol de dependencias.
Si por alguna razón no queremos que se incluya un artefacto que se agrega de forma transitiva podemos indicar a Maven que no lo haga:
<project>
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.5.ga</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
...
</project> Finalmente, podemos ver el árbol de dependencias del que se hace uso en nuestro proyecto mediante el comando mvn dependency:tree.
Dependency Management
Cuando se tiene un conjunto de proyectos que heredan de un mismo padre y, por lo tanto, comparten dependencias comunes, es posible agrupar esas dependencias compartidas en el pom común y hacer referencias a dicho grupo desde los pom secundarios. En el pom compartido especificaríamos las dependencias agrupadas de la siguiente forma
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.pruebas</groupId>
<artifactId>mi-prueba</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtimw</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
</project> y en un proyecto hijo haríamos referencia a ellas mediante
<project>
...
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.pruebas</groupId>
<artifactId>mi-prueba</artifactId>
<type>war</type>
</dependency>
</dependencies>
...
</project> Para las artefactos cuyo <type> es diferente a jar es necesario especificarlo. Esto es debido a que la información mínima para hacer referencia a una dependencia agrupada en <dependencyManagement> es {groupId, artifactId, type, classifier} y por defecto se toma<type> como jar y <classifier> como null.
Un beneficio obtenido de agrupar las dependencias de esta forma es que, si en un futuro queremos cambiar la versión a utilizar de un determinado artefacto, con solo cambiar el valor de versión en el pom padre ya tendremos ésta versión disponible en los proyectos que lo referencie.
Rangos de versión de las dependencias
Al indicar la versión de una dependencia es posible establecer un rango de los valores a admitir en lugar de indicar un valor concreto. Esto se consigue utilizando los caracteres (,), y [,]. El primer par permite excluir valores mientras que el segundo los incluye. Por ejemplo:
<version>[3.8,4.0)</version>significa una versión igual o mayor que 3.8 y menor que 4.0<version>[4.0,)</version>significa cualquier versión igual o mayor que 4.0<version>[,3.8)</version>significa cualquier versión igual o menor que 3.8<version>[2.0]</version>significa solo la versión 2.0, ninguna otra
Hay que tener en ccuenta que cuando se declara el valor de versión de forma normal, es decir, sin usar estos caracteres delimitadores, el comportamiento de Maven es utilizar cualquier versión pero preferiblemente la indicada. Por tanto, en caso de conflicto buscará la versión más adecuada. Si se usa la forma del último ejemplo puede generarse algún error si se indica una versión diferente en alguna otra dependencia.
Para más información: Introduction to the Dependency Mechanism