¿Para qué se usa el complemento maven-shade-plugin y por qué querrías reubicar los paquetes de Java?

282

Encontré el plugin maven-shade-plugin que se usa en el pom.xml de alguien. Nunca he usado el plugin maven-shade-plugin antes (y soy un Maven n00b), así que traté de entender la razón para usar esto y lo que hace.

Miré los documentos de Maven , sin embargo, no puedo entender esta declaración:

"Este complemento proporciona la capacidad de empaquetar el artefacto en un uber-jar, incluidas sus dependencias y sombrear, es decir, cambiar el nombre de los paquetes de algunas de las dependencias".

La documentación en la página no parece muy amigable para los novatos.

¿Qué es un "uber jar"? ¿Por qué alguien querría hacer uno? ¿Cuál es el punto de renombrar los paquetes de las dependencias? Traté de revisar los ejemplos en la página apache de maven-shade-plugin como "Seleccionar contenido para Uber Jar", pero aún no puedo entender lo que se está logrando con "sombreado".

Se agradecería cualquier puntero a ejemplos ilustrativos / casos de uso (con una explicación de por qué se requería sombreado en este caso, qué problema está resolviendo). Por último, ¿cuándo debo usar el complemento maven-shade-plugin?

no ser
fuente
16
Para nombrar "uber jar" tengo la única asociación con el alemán, donde "über" significa "sobre". Juntos, literalmente, significa "jar-over-all-other-jars". "Sombrear" es lo mismo que "reasignación de paquetes" que se necesita para las clases o recursos que colisionan.
dma_k
1
s/reallocation/relocation/en el comentario anterior
Christopher Schultz
12
El frasco súper es como un anillo: un frasco para gobernarlos a todos, un frasco para encontrarlos, un frasco para traerlos a todos y en la oscuridad los atas.
Marti Nito

Respuestas:

343

Uber JAR, en resumen, es un JAR que contiene todo.

Normalmente en Maven, confiamos en la gestión de dependencias. Un artefacto contiene solo las clases / recursos de sí mismo. Maven será responsable de averiguar todos los artefactos (JAR, etc.) que el proyecto dependerá de cuándo se construya el proyecto.

Un uber-jar es algo que toma todas las dependencias, extrae el contenido de las dependencias y las coloca con las clases / recursos del proyecto en un gran JAR. Al tener tal uber-jar, es fácil de ejecutar, ya que solo necesitará un JAR grande en lugar de toneladas de JAR pequeños para ejecutar su aplicación. También facilita la distribución en algunos casos.

Solo una nota al margen. Evite usar uber-jar como dependencia de Maven, ya que está arruinando la función de resolución de dependencia de Maven. Normalmente, creamos uber-jar solo para el artefacto final para la implementación real o para la distribución manual, pero no para colocarlo en el repositorio de Maven.


Actualización: Acabo de descubrir que no he respondido una parte de la pregunta: "¿Cuál es el punto de renombrar los paquetes de las dependencias?". Aquí hay algunas actualizaciones breves y con suerte ayudará a las personas que tengan preguntas similares.

Crear uber-jar para facilitar la implementación es un caso de uso del complemento de sombra. También hay otros casos de uso comunes que implican el cambio de nombre del paquete.

Por ejemplo, estoy desarrollando una Foobiblioteca, que depende de una versión específica (por ejemplo, 1.0) de la Barbiblioteca. Suponiendo que no puedo hacer uso de otra versión de Barlib (debido a un cambio de API u otros problemas técnicos, etc.). Si simplemente declaro Bar:1.0como Foodependencia de Maven, es posible caer en un problema: un Quxproyecto depende de Foo, y también Bar:2.0(y no puede usarlo Bar:1.0porque Quxnecesita usar una nueva función Bar:2.0). Aquí está el dilema: ¿debería Quxusar Bar:1.0(qué Quxcódigo no funcionará) o Bar:2.0(qué Foocódigo no funcionará)?

Para resolver este problema, el desarrollador de Foopuede elegir usar el complemento de sombra para cambiar el nombre de su uso Bar, de modo que todas las clases en Bar:1.0jar estén incrustadas en Foojar, y el paquete de las Barclases incrustadas cambie de com.bara com.foo.bar. Al hacerlo, Quxpuede depender de forma segura Bar:2.0porque ahora Fooya no depende Bary está utilizando su propia copia "alterada" Barubicada en otro paquete.

Adrian Shum
fuente
55
Gracias, eso me ayuda mucho. ¿Se llama "sombreado" porque oculta las dependencias y otros frascos dentro del uber-jar?
ser
66
No estoy realmente seguro de la razón detrás del nombre, pero desde su portada, parece que el "sombreado" describe la "ocultación" de las dependencias. Como opcionalmente puede incluir algunas dependencias en el JAR sombreado, el complemento de sombra también generará un POM correcto para usted que eliminará las dependencias incluidas. Parece que el sombreado describe este proceso
Adrian Shum el
1
@AdrianShum: ¿Puedes sugerir cómo crear un frasco súper, sin usar el complemento de sombra? ¿O específicamente cómo resolver la "ruina de la función de resolución de dependencia de Maven"?
Suraj Menon
3
@SurajMenon: Usar el complemento Shade es la forma más fácil de crear uber-jar. Sin embargo, también puede usar el complemento Ensamblaje y el jar-with-dependencydescriptor incorporado . Para el problema de la solución de dependencia usando uber-jar, ya he mencionado en mi respuesta: No use uber-jar como dependencia, punto. Un poco más en detalle: antes de crear el uber-jar, debe tener un proyecto normal con dependencia normal. Ese artefacto original es el que debes usar como dependencia (en lugar del uber-jar)
Adrian Shum
1
Solo una pregunta menor: ¿deja Class.forNamede funcionar?
SOFe
64

Me preguntaba recientemente por qué Elasticsearch sombrea y reubica algunas (pero no todas) de sus dependencias. Aquí hay una explicación del responsable del proyecto, @kimchy :

La parte sombreada es intencional, las bibliotecas sombreadas que utilizamos en elasticsearch son para todo propósito y parte de elasticsearch, la versión utilizada está estrechamente vinculada a lo que expone elasticsearch y cómo utiliza la biblioteca en función de las funciones internas de la biblioteca (y que cambia entre versiones), netty y guayaba son excelentes ejemplos.

Por cierto, no tengo ningún problema con proporcionar varios frascos de búsqueda elástica, uno con lucene sin sombra y otro con Lucene sombreado. Sin embargo, no estoy seguro de cómo hacerlo con Maven. No quiero proporcionar una versión que no sombree netty / jackson, por ejemplo, debido al uso profundo e intimido que elasticsearch tiene con ellos (por ejemplo, usar la próxima mejora de amortiguación con cualquier versión anterior de netty, excepto la actual) en realidad usa más memoria en comparación con usar considerablemente menos).

- https://github.com/elasticsearch/elasticsearch/issues/2091#issuecomment-7156766

Y otro aquí de drewr :

El sombreado es importante para mantener nuestras dependencias (especialmente netty, lucene, guava) cerca de nuestro código para que podamos solucionar un problema, incluso si el proveedor ascendente se queda atrás. Es posible que distribuyamos versiones modularizadas del código, lo que ayudaría con su problema particular (# 2091, por ejemplo), pero no podemos simplemente eliminar las dependencias sombreadas en este momento. Puede crear una versión local de ES para sus propósitos hasta que haya una mejor solución.

- https://github.com/elasticsearch/elasticsearch/pull/3244#issuecomment-20125452

Entonces, ese es un caso de uso. En cuanto a un ejemplo ilustrativo, a continuación se muestra cómo se utiliza maven-shade-plugin en pom.xml de elasticsearch (v0.90.5). Las artifactSet::includelíneas le indican qué dependencias extraer en el uber JAR (básicamente, se descomprimen y se vuelven a empaquetar junto con las propias clases de elasticsearch cuando se produce el jar de elasticsearch de destino. solo un archivo ZIP que contiene las clases, los recursos, etc. del programa y algunos metadatos. Puede extraer uno para ver cómo se combina).

Las relocations::relocationlíneas son similares, excepto que en cada caso también aplican las sustituciones especificadas a las clases de la dependencia; en este caso, las clasificamos org.elasticsearch.common.

Finalmente, la filterssección excluye algunas cosas del JAR de destino que no deberían estar allí, como metadatos JAR, archivos de construcción de ant, archivos de texto, etc. que están empaquetados con algunas dependencias, pero que no pertenecen a un JAR superior.

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.1</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <minimizeJar>true</minimizeJar>
            <artifactSet>
                <includes>
                    <include>com.google.guava:guava</include>
                    <include>net.sf.trove4j:trove4j</include>
                    <include>org.mvel:mvel2</include>
                    <include>com.fasterxml.jackson.core:jackson-core</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-smile</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</include>
                    <include>joda-time:joda-time</include>
                    <include>io.netty:netty</include>
                    <include>com.ning:compress-lzf</include>
                </includes>
            </artifactSet>
            <relocations>
                <relocation>
                    <pattern>com.google.common</pattern>
                    <shadedPattern>org.elasticsearch.common</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>gnu.trove</pattern>
                    <shadedPattern>org.elasticsearch.common.trove</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166y</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166y</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166e</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166e</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.mvel2</pattern>
                    <shadedPattern>org.elasticsearch.common.mvel2</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.fasterxml.jackson</pattern>
                    <shadedPattern>org.elasticsearch.common.jackson</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.joda</pattern>
                    <shadedPattern>org.elasticsearch.common.joda</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.jboss.netty</pattern>
                    <shadedPattern>org.elasticsearch.common.netty</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.ning.compress</pattern>
                    <shadedPattern>org.elasticsearch.common.compress</shadedPattern>
                </relocation>
            </relocations>
            <filters>
                <filter>
                    <artifact>*:*</artifact>
                    <excludes>
                        <exclude>META-INF/license/**</exclude>
                        <exclude>META-INF/*</exclude>
                        <exclude>META-INF/maven/**</exclude>
                        <exclude>LICENSE</exclude>
                        <exclude>NOTICE</exclude>
                        <exclude>/*.txt</exclude>
                        <exclude>build.properties</exclude>
                    </excludes>
                </filter>
            </filters>
        </configuration>
    </plugin>
</plugins>
Tom
fuente
2

Pequeña advertencia

Aunque eso no describe por qué a uno le gustaría usar el plugin maven-shade-plugin (ya que la respuesta seleccionada lo describe bastante bien), me gustaría señalar que tuve problemas con él. Cambió el JAR (ya que eso es lo que está haciendo) y causó una regresión en mi software.

Entonces, en lugar de usar esto (o el plugin maven-jarjar-plugin), he usado el binario de JarJar que parece funcionar sin problemas.

Estoy publicando aquí mi solución, ya que me tomó un tiempo encontrar una solución decente.


Archivo JAR de Downlaod JarJar

Puede descargar el jar desde aquí: https://code.google.com/p/jarjar/ En el menú de la izquierda tiene un enlace para descargarlo.


Cómo usar JarJar para reubicar las clases de un JAR de un paquete a otro

En este ejemplo, cambiaremos el paquete de "com.fasterxml.jackson" a "io.kuku.dependencies.com.fasterxml.jackson". - El JAR de origen se llama "jackson-databind-2.6.4.jar" y el nuevo JAR modificado (objetivo) se llama "kuku-jackson-databind-2.6.4.jar". - El archivo JAR "jarjar" está en la versión 1.4

  1. Cree un archivo "rules.txt". El contenido del archivo debe ser (ver el período anterior al carácter '@'): regla com.fasterxml.jackson. ** io.kuku.dependencies.com.fasterxml.jackson. @ 1

  2. Ejecute el siguiente comando: java -jar jarjar-1.4.jar process rules.txt jackson-databind-2.6.4.jar kuku-jackson-databind-2.6.4.jar


Instalar los JAR modificados en el repositorio local

En este caso, estoy instalando 3 archivos ubicados en la carpeta "c: \ my-jars \".

mvn install: install-file -Dfile = C: \ my-jars \ kuku-jackson-annotations-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-annotations -Dversion = 2.6.4 - Dpackaging = jar

mvn install: install-file -Dfile = C: \ my-jars \ kuku-jackson-core-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-core -Dversion = 2.6.4 - Dpackaging = jar

mvn install: install-file -Dfile = C: \ my-jars \ kuku-jackson-databind-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-annotations -Dversion = 2.6.4 - Dpackaging = jar


Usando los JAR modificados en el pom del proyecto

En este ejemplo, este es el elemento "dependencias" en los proyectos pom:

<dependencies>
    <!-- ================================================== -->
    <!-- kuku JARs -->
    <!-- ================================================== -->
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-annotations</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-core</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-databind</artifactId>
        <version>2.6.4</version>
    </dependency>
</dependencies>
nadavy
fuente
1
Gracias por esta sugerencia alternativa. Esto no era lo que estaba buscando, pero resultó ser una solución mucho más simple y rápida para aplicar traducciones de paquetes de una sola vez en bibliotecas heredadas que nunca cambiarán.
Frelling
¿Ha descubierto la razón de tales fallas comparando el contenido de sus respectivos resultados?
tribbloid
2

Creo que un ejemplo de la necesidad de un jar "sombreado" es una función AWS Lambda. Parece que solo le permiten cargar 1 jar, no una colección completa de .jars como la que encontraría en un archivo .war típico. Por lo tanto, crear un único .jar con todas las dependencias del proyecto le permite hacer esto.

atom88
fuente