java - que - maven tutorial español
Enfoque sistemático con Maven para lidiar con el infierno de la dependencia. (4)
Estoy luchando con la forma de abordar el infierno dependencia de jar. Tengo un proyecto Maven-IntelliJ Scala que usa algunos aws sdk''s. Recientemente agregando el kinesis sdk ha introducido versiones incompatibles de Jackson.
Mi pregunta es : ¿cómo enfoco sistemáticamente el problema del infierno de Jar?
Entiendo a los cargadores de clases y cómo Maven elige entre tarros duplicados, pero todavía no estoy al tanto de los pasos prácticos reales para solucionar el problema.
Mis intentos en este momento se basan en la prueba y el error, y describo aquí con el ejemplo de Jackson:
- Primero, veo cuál es la excepción real, en este caso NoSuchMethodError, en la clase ObjectMapper de enlaces de datos de Jackson. Luego miro los documentos de Jackson para ver cuándo se agregó o eliminó el método. Esto suele ser bastante tedioso, ya que verifico manualmente los api docs para cada versión (pregunta 1: ¿hay alguna manera mejor?) .
- Luego, uso
mvn dependency:tree
para averiguar qué versión de Jackson estoy usando en realidad (pregunta 2: ¿existe una forma automática de preguntar a Maven qué versión de un tarro está en uso, en lugar de peinar la salida del árbol?) . - Finalmente, comparo la
mvn dependency:tree
salida antes de agregar el Kinesis SDK, y luego, para detectar diferencias en lamvn dependency:tree
salida, y espero ver si la versión de Jackson cambió. (pregunta 3: ¿Cómo utiliza Maven las bibliotecas en los frascos sombreados, cuando se produce una resolución de dependencia? ¿Igual que cualquier otra?) .
Finalmente, después de comparar las salidas del árbol, trato de agregar la última versión funcional de Jackson explícitamente en el POM, para activar la prioridad en la cadena de resolución de dependencias de Maven. Si la última no funciona, agrego la siguiente versión más reciente, y así sucesivamente.
Todo este procedimiento es increíblemente tedioso. Además de las preguntas específicas que formulé, también siento curiosidad por los enfoques sistémicos de otras personas para este problema. ¿Alguien tiene algún recurso que use?
Luego miro los documentos de Jackson para ver cuándo se agregó o eliminó el método. Esto suele ser bastante tedioso, ya que verifico manualmente los api docs para cada versión (pregunta 1: ¿hay alguna manera mejor?)
Para verificar la compatibilidad de la API (ruptura) hay varias herramientas que analizarían automáticamente los archivos jar y le proporcionarían la información correcta. Desde this post de hay consejos útiles para algunas herramientas útiles.
JAPICC parece bastante bueno.
Luego, uso
mvn dependency:tree
para averiguar qué versión de Jackson estoy usando en realidad (pregunta 2: ¿existe una forma automática de preguntar a Maven qué versión de un tarro está en uso, en lugar de peinar la salida del árbol?)
El maven-dependency-tree
es definitivamente el camino a seguir, pero puede filtrar desde el principio el alcance y solo obtener lo que realmente está buscando, usando la opción de inclusión de la siguiente manera:
mvn dependency:tree -Dincludes=<groupId>
nota: también puede proporcionar más información sobre la opción de groupId:artifactId:type:version
en el formulario groupId:artifactId:type:version
o usar comodines como *:artifactId
.
Parece una pequeña pista, pero en proyectos grandes con muchas dependencias que reducen su producción es de gran ayuda. Normalmente, simplemente el groupId
debería ser suficiente como filtro, el *:artifactId
es probablemente el más rápido, sin embargo, si está buscando una dependencia específica.
Si está interesado en una list de dependencias (no como un árbol) también ordenadas alfabéticamente (bastante útil en muchos escenarios), lo siguiente también puede ayudar:
mvn dependency:list -Dsort=true -DincludeGroupIds=groupId
pregunta 3: ¿Cómo utiliza Maven las bibliotecas en los tarros sombreados cuando se produce una resolución de dependencia? Igual que cualquier otro?
Por frascos sombreados puede significar:
- frascos de grasa , que también traen otros frascos en el classpath. En este caso, se ven como una dependencia, una unidad para la Mediación de Dependencia de Maven , su contenido sería parte de la ruta de clase del proyecto. En general, no debe tener frascos grandes como parte de sus dependencias ya que no tiene control sobre las bibliotecas empaquetadas que trae.
- tarros con paquetes sombreados (renombrados) . En este caso, nuevamente, no hay control en lo que respecta a la Mediación de Dependencia de Maven: es una unidad, un frasco, basado en su GAVC (GroupId, ArtifactId, Version, Classifier) lo que lo hace único. Luego, su contenido se agrega a la ruta de clase del proyecto (de acuerdo con el scope la dependencia, pero como se cambió el nombre de su paquete, es posible que tenga conflictos difíciles de manejar. Nuevamente, no debería haber renombrado paquetes como parte de las dependencias de su proyecto (pero a menudo no puedes saber eso).
¿Alguien tiene algún recurso que use?
En general, debe comprender bien cómo Maven maneja las dependencias y utilizar los recursos que ofrece (sus herramientas y mecanismos). A continuación algunos puntos importantes:
-
dependencyManagement
es definitivamente el punto de entrada en este tema: aquí puede tratar con la mediación de dependencia de Maven, influir en su decisión sobre las dependencias transitivas, sus versiones, su alcance. Un punto importante es: lo que agrega a ladependencyManagement
dedependencyManagement
no se agrega automáticamente como una dependencia.dependencyManagement
solo se tiene en cuenta una vez que una determinada dependencia del proyecto (como se declara en el archivopom.xml
o mediante dependencias transitivas) tiene una coincidencia con una de sus entradas, de lo contrario, simplemente se ignoraría. Es una parte importante delpom.xml
ya que ayuda a gobernar las dependencias y sus gráficos transitivos y es por eso que a menudo se usa en poms primarios: quiere manejar solo una y de manera centralizada de qué versión de, por ejemplo,log4j
desea en todos sus proyectos de Maven, lo declara en un pom padre común / compartido y su gestión dedependencyManagement
y se asegura de que se utilizará como tal. La centralización significa mejor gobierno y mejor mantenimiento. -
dependency
sección dedependency
es importante para declarar dependencias: normalmente, debe declarar aquí solo las dependencias directas que necesita. Una buena regla general es: declare aquí como ámbito decompile
(el valor predeterminado) solo lo que realmente usa como declaración deimport
en su código (pero a menudo debe ir más allá de eso, por ejemplo, el controlador JDBC se requiere en tiempo de ejecución y nunca se menciona en su código). , entonces estaría en elruntime
deruntime
sin embargo). También recuerde: el orden de la declaración es importante: la primera dependencia declarada gana en caso de conflicto contra una dependencia transitiva, por lo tanto, al volver a declarar de manera simple una dependencia, puede influir efectivamente en la mediación de la dependencia. - No abuse de las
exclusions
en dependencias para manejar dependencias transitivas: use ladependencyManagement
y el orden de lasdependencies
para eso, si puede. El abuso de lasexclusions
hace que el mantenimiento sea mucho más difícil, utilícelo solo si realmente lo necesita. Además, cuando agregueexclusions
agregue siempre un comentario XML que explique por qué: sus compañeros de equipo y / o su futuro lo apreciarán. - Usa las dependencias del
scope
pensativamente . Use el alcance predeterminado (compile
) para lo que realmente necesita para la compilación y prueba (por ejemplo,loga4j
), use solotest
(y solo) para lo que se usa bajo prueba (por ejemplo,junit
),junit
cuenta el alcanceprovided
por lo que ya proporciona su contenedor de destino (por ejemplo,servlet-api
), use el alcance deruntime
solo para lo que necesita en el tiempo de ejecución, pero nunca debe compilar con él (por ejemplo, los controladores JDBC). No utilice el ámbito delsystem
ya que solo implicaría problemas (por ejemplo, no está empaquetado con su artefacto final). - No juegue con rangos de versión , a menos que sea por razones específicas y tenga en cuenta que la versión especificada es un requisito mínimo de forma predeterminada, la expresión
[<version>]
es la más fuerte, pero rara vez la necesitaría. - use la
property
Maven como marcador de posición para el elemento deversion
de las familias de bibliotecas para asegurarse de tener un lugar centralizado para el control de versiones de un conjunto de dependencias que tendrían el mismo valor de versión. Un ejemplo clásico sería una propiedadspring.version
ohibernate.version
para usar para varias dependencias. Una vez más, la centralización significa una mejor administración y mantenimiento, lo que también significa menos dolor de cabeza y menos infierno . - Cuando se proporcione, importe BOM como alternativa al punto anterior y para manejar mejor las familias de dependencias (por ejemplo, jboss ), delegando a otro archivo
pom.xml
la gestión de un determinado conjunto de dependencias. - No (ab) use las dependencias de
SNAPSHOT
(o lo menos posible). Si realmente lo necesita, asegúrese de no soltar nunca utilizando una dependenciaSNAPSHOT
: la reproducibilidad de la compilación estará en alto peligro de lo contrario. - Al resolver problemas, siempre verifique la jerarquía completa de su archivo
pom.xml
, usandohelp:effective-pom
puede ser realmente útil al verificar la administración dedependencyManagement
,dependencies
yproperties
efectivas en lo que respecta al gráfico de dependencia final. - Usa algunos otros complementos de Maven para ayudarte en el gobierno . El
maven-dependency-plugin
es realmente útil durante la resolución de problemas, pero también elmaven-enforcer-plugin
viene a ayudar. Aquí hay algunos ejemplos que vale la pena mencionar:
El siguiente ejemplo se asegurará de que nadie (usted, sus compañeros de equipo, su futuro) pueda agregar una biblioteca de prueba conocida en el ámbito de compile
: la compilación fallará. Se asegura de que junit
nunca llegue a PROD (empaquetado con su war
, por ejemplo)
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1<.version>
<executions>
<execution>
<id>enforce-test-scope</id>
<phase>validate</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>junit:junit:*:*:compile</exclude>
<exclude>org.mockito:mockito-*:*:*:compile</exclude>
<exclude>org.easymock:easymock*:*:*:compile</exclude>
<exclude>org.powermock:powermock-*:*:*:compile</exclude>
<exclude>org.seleniumhq.selenium:selenium-*:*:*:compile</exclude>
<exclude>org.springframework:spring-test:*:*:compile</exclude>
<exclude>org.hamcrest:hamcrest-all:*:*:compile</exclude>
</excludes>
<message>Test dependencies should be in test scope!</message>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
Eche un vistazo a otras reglas estándar que ofrece este complemento: muchas podrían ser útiles para romper la compilación en caso de escenarios incorrectos:
- puede prohibir una dependencia (incluso transitiva ), muy útil en muchos casos
- puede fallar en el caso de que se use
SNAPSHOT
, útil en un perfil de lanzamiento, como ejemplo.
Una vez más, un pom padre común podría incluir más de uno de estos mecanismos (administración de dependencyManagement
, complemento ejecutor, propiedades para familias de dependencias) y asegurarse de que se respeten ciertas reglas. Puede que no cubra todos los escenarios posibles, pero definitivamente disminuiría el grado de infierno que percibe y experimenta.
En mi experiencia, no encontré nada completamente automatizado, pero encontré el siguiente enfoque bastante sistemático y útil para mí:
En primer lugar, trato de tener un mapa claro de la estructura del proyecto, las relaciones entre los proyectos y normalmente uso la vista de dependencia gráfica de Eclipse, que me dice, por ejemplo, si se omite una dependencia para el conflicto con otra. Además te dice las dependencias resueltas para el proyecto. Sinceramente, no uso IntelliJ IDEA pero creo que tiene una característica similar.
Por lo general, trato de poner una dependencia muy común más alta en la estructura y exploto la <dependencyManagement>
para cuidar la versión de las dependencias transitivas y, lo más importante, para evitar duplicados en la estructura del proyecto.
En esta publicación del blog Maven - Manage Dependencies puede encontrar un buen tutorial sobre la administración de dependencias.
Al agregar una nueva dependencia a mi proyecto, como en su caso, me ocupo de dónde se agrega en la estructura de mi proyecto y realizo los cambios correspondientes, pero en la mayoría de los casos el mecanismo de administración de dependencias es capaz de lidiar con este problema.
En esta publicación del blog Maven Best Practices puedes encontrar:
La sección de administración de dependencias de Maven permite que un pom.xml principal defina dependencias que se pueden reutilizar en proyectos secundarios. Esto evita la duplicación; sin la sección dependencyManagement, cada proyecto hijo debe definir su propia dependencia y duplicar la versión, el alcance y el tipo de la dependencia.
Obviamente, si necesita una versión particular de una dependencia para un proyecto, siempre puede especificar la versión que necesita localmente, en lo más profundo de la jerarquía.
Estoy de acuerdo con usted, podría ser bastante tedioso, pero la administración de dependencias podría brindarle una buena ayuda.
Incluso al reemplazar todo el tarro con el mismo nombre, aún puede tener algunas clases con el mismo nombre completo. Utilicé el plugin maven shade en uno de mis proyectos. Imprime clases con el mismo nombre calificado que vienen de diferentes tarros. Tal vez eso pueda ayudarte
Use el complemento de Maven Helper para resolver fácilmente todos los conflictos al excluir versiones antiguas de dependencias.