À quoi sert le plugin maven-shadow-plugin, et pourquoi voudriez-vous déplacer des packages Java?

282

J'ai trouvé le plugin maven-shadow utilisé dans le fichier pom.xml de quelqu'un. Je n'ai jamais utilisé de plugin maven-shadow avant (et je suis un Maven n00b), j'ai donc essayé de comprendre la raison de son utilisation et ce qu'il fait.

J'ai regardé les documents Maven , mais je ne comprends pas cette déclaration:

"Ce plugin offre la possibilité de conditionner l'artefact dans un uber-jar, y compris ses dépendances et de masquer - c'est-à-dire renommer - les packages de certaines des dépendances."

La documentation sur la page ne semble pas très adaptée aux débutants.

Qu'est-ce qu'un "pot uber?" Pourquoi voudrait-on en faire un? Quel est l'intérêt de renommer les packages des dépendances? J'ai essayé de passer en revue les exemples sur la page apache de maven-shadow-plugin tels que "Sélection du contenu pour Uber Jar", mais je ne comprends toujours pas ce qui est accompli avec "ombrage".

Tout pointeur vers des exemples / cas d'utilisation illustratifs (avec une explication de la raison pour laquelle l'ombrage était nécessaire dans ce cas - quel problème résout-il) serait apprécié. Enfin, quand dois-je utiliser le plugin maven-shadow-plugin?

ne pas être
la source
16
Pour la dénomination "uber jar", j'ai la seule association avec l'allemand, où "über" signifie "fini". Ensemble, cela signifie littéralement "jar-over-all-other-jars". "Ombrage" est identique à "réallocation de package" qui est nécessaire pour les classes ou les ressources qui entrent en collision.
dma_k
1
s/reallocation/relocation/dans le commentaire ci-dessus.
Christopher Schultz
12
Le pot uber est comme un anneau: un pot pour les gouverner tous, un pot pour les trouver, un pot pour les apporter tous et dans l'obscurité les lier.
Marti Nito

Réponses:

343

Uber JAR, en bref, est un JAR contenant tout.

Normalement, à Maven, nous comptons sur la gestion des dépendances. Un artefact ne contient que les classes / ressources de lui-même. Maven sera responsable de découvrir tous les artefacts (JAR, etc.) du projet en fonction de la date de construction du projet.

Un uber-jar est quelque chose qui prend toutes les dépendances, extrait le contenu des dépendances et les met avec les classes / ressources du projet lui-même, dans un grand JAR. En ayant un tel uber-jar, il est facile à exécuter, car vous aurez besoin d'un seul grand JAR au lieu de tonnes de petits JAR pour exécuter votre application. Il facilite également la distribution dans certains cas.

Juste une petite note. Évitez d'utiliser uber-jar comme dépendance Maven, car cela ruine la fonctionnalité de résolution des dépendances de Maven. Normalement, nous créons uber-jar uniquement pour l'artefact final pour un déploiement réel ou pour une distribution manuelle, mais pas pour le placement dans le référentiel Maven.


Mise à jour: Je viens de découvrir que je n'ai pas répondu à une partie de la question: "Quel est l'intérêt de renommer les packages des dépendances?". Voici quelques brèves mises à jour et, espérons-le, aideront les personnes ayant une question similaire.

Créer un uber-jar pour faciliter le déploiement est un cas d'utilisation du plugin d'ombre. Il existe également d'autres cas d'utilisation courants qui impliquent un changement de nom de package.

Par exemple, je développe une Foobibliothèque, qui dépend d'une version spécifique (par exemple 1.0) de la Barbibliothèque. En supposant que je ne peux pas utiliser une autre version de Barlib (en raison d'un changement d'API ou d'autres problèmes techniques, etc.). Si je déclare simplement Bar:1.0que Foola dépendance « dans Maven, il est possible de tomber dans un problème: un Quxprojet dépend de Foo, et aussi Bar:2.0(et il ne peut pas utiliser Bar:1.0parce que les Quxbesoins d'utiliser nouvelle fonctionnalité Bar:2.0). Voici le dilemme: devrait Quxutiliser Bar:1.0(quel Quxcode ne fonctionnera pas) ou Bar:2.0(quel Foocode ne fonctionnera pas)?

Afin de résoudre ce problème, le développeur de Foopeut choisir d'utiliser le plugin d'ombre pour renommer son utilisation Bar, de sorte que toutes les classes dans Bar:1.0jar soient incorporées dans Foojar et que le package des Barclasses incorporées soit changé de com.baren com.foo.bar. Ce faisant, Quxpeut dépendre en toute sécurité Bar:2.0car maintenant Foone dépend plus de Bar, et il utilise sa propre copie de "altéré" Barsitué dans un autre package.

Adrian Shum
la source
5
Merci, ça aide beaucoup. Est-il appelé "ombrage" parce qu'il cache les dépendances et autres pots à l'intérieur du pot uber?
nonbeing
6
Je ne suis pas vraiment sûr de la raison derrière le nom, mais d'après sa première page, il semble suggérer que le "shading" décrit la "dissimulation" des dépendances. Comme vous pouvez éventuellement inclure certaines dépendances dans le JAR ombré, le plugin d'ombre générera également un POM correct pour vous qui supprimera les dépendances incluses. Il semble que l'ombrage décrive ce processus
Adrian Shum
1
@AdrianShum: Pouvez-vous suggérer comment créer un pot Uber, sans utiliser le plugin d'ombre? Ou précisément comment résoudre la "ruine de la fonction de résolution des dépendances de Maven"?
Suraj Menon
3
@SurajMenon: L'utilisation du plugin Shade est le moyen le plus simple de créer un uber-jar. Cependant, vous pouvez également utiliser le plug-in Assembly et utiliser le jar-with-dependencydescripteur intégré . Pour le problème de la dépendance de la roslution avec uber-jar, j'ai déjà mentionné dans ma réponse: N'utilisez pas uber-jar comme dépendance, point final. Un peu plus en détail: avant de créer l'uber-jar, vous devez avoir un projet normal avec une dépendance normale. Cet artefact d'origine est celui que vous devez utiliser comme dépendance (au lieu du pot d'uber)
Adrian Shum
1
Juste une question mineure: cela ne Class.forNamefonctionne- t-il pas ?
SOFe
64

Je me demandais récemment pourquoi elasticsearch ombrage et délocalise quelques-unes (mais pas toutes) de ses dépendances. Voici une explication du responsable du projet, @kimchy :

La partie ombrée est intentionnelle, les bibliothèques ombrées que nous utilisons dans elasticsearch font partie de toutes les intentions et fins d'elasticsearch, la version utilisée est étroitement liée à ce qu'elasticsearch expose et à la façon dont elle utilise la bibliothèque en fonction des caractéristiques internes du fonctionnement de la bibliothèque (et qui change entre les versions), netty et goyave sont de bons exemples.

Btw, je n'ai aucun problème à fournir plusieurs bocaux elasticsearch, un avec lucene non ombré et un avec Lucene ombragé. Je ne sais pas comment le faire avec maven. Je ne veux pas fournir une version qui n'ombrage pas netty / jackson par exemple, en raison de l'utilisation intimiste profonde qu'elasticsearch a avec eux (par exemple, en utilisant l'amélioration de tamponnage à venir avec n'importe quelle version précédente de netty, à l'exception de la version actuelle) utilisent en fait plus de mémoire que d’en utiliser beaucoup moins.

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

Et un autre ici de drewr :

L'ombrage est important pour garder nos dépendances (notamment netty, lucene, goyave) proches de notre code afin que nous puissions résoudre un problème même si le fournisseur en amont est en retard. Il est possible que nous distribuions des versions modulaires du code, ce qui aiderait à résoudre votre problème particulier (# 2091 par exemple), mais nous ne pouvons pas simplement supprimer les dépendances ombrées pour le moment. Vous pouvez créer une version locale d'ES pour vos besoins jusqu'à ce qu'il y ait une meilleure solution.

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

Voilà donc un cas d'utilisation. Comme pour un exemple illustratif, voici comment le plugin maven-shadow-plugin est utilisé dans le fichier pom.xml d'elasticsearch (v0.90.5). Les artifactSet::includelignes lui indiquent les dépendances à insérer dans le JAR uber (en gros, elles sont décompressées et reconditionnées aux côtés des propres classes d'Elasticsearch lorsque le pot Elasticsearch cible est produit. (Si vous ne le saviez pas déjà, un fichier JAR est juste un fichier ZIP qui contient les classes du programme, les ressources, etc., et quelques métadonnées. Vous pouvez en extraire un pour voir comment il est assemblé.)

Les relocations::relocationlignes sont similaires, sauf que dans chaque cas, elles appliquent également les substitutions spécifiées aux classes de la dépendance - dans ce cas, en les regroupant org.elasticsearch.common.

Enfin, la filterssection exclut certaines choses du JAR cible qui ne devraient pas y être - telles que les métadonnées JAR, les fichiers de construction de fourmis, les fichiers texte, etc. qui sont emballés avec certaines dépendances, mais qui n'appartiennent pas à un JAR uber.

<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>
À M
la source
2

Petit avertissement

Bien que cela ne décrive pas pourquoi on aimerait utiliser le plugin maven-shadow-plugin (puisque la réponse sélectionnée le décrit assez bien), je voudrais noter que j'ai eu des problèmes avec lui. Cela a changé le JAR (puisque c'est ce qu'il fait) et cela a provoqué une régression dans mon logiciel.

Donc, au lieu d'utiliser ceci (ou le plugin maven-jarjar), j'ai utilisé le binaire de JarJar qui semble fonctionner sans problème.

Je poste ici ma solution car il m'a fallu un certain temps pour trouver une solution décente.


Télécharger le fichier JAR de JarJar

Vous pouvez télécharger le pot ici: https://code.google.com/p/jarjar/ Dans le menu de gauche, vous avez un lien pour le télécharger.


Comment utiliser JarJar pour déplacer des classes d'un JAR d'un package à un autre

Dans cet exemple, nous allons changer le package de "com.fasterxml.jackson" en "io.kuku.dependencies.com.fasterxml.jackson". - Le JAR source est appelé "jackson-databind-2.6.4.jar" et le nouveau JAR modifié (cible) est appelé "kuku-jackson-databind-2.6.4.jar". - Le fichier JAR "jarjar" est en version 1.4

  1. Créez un fichier "rules.txt". Le contenu du fichier doit être (observez la période précédant le caractère '@'): règle com.fasterxml.jackson. ** io.kuku.dependencies.com.fasterxml.jackson. @ 1

  2. Exécutez la commande suivante: java -jar jarjar-1.4.jar process rules.txt jackson-databind-2.6.4.jar kuku-jackson-databind-2.6.4.jar


Installation des fichiers JAR modifiés dans le référentiel local

Dans ce cas, j'installe 3 fichiers situés dans le dossier "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 = pot

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 = pot

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 = pot


Utilisation des fichiers JAR modifiés dans le pom du projet

Dans cet exemple, il s'agit de l'élément "dépendances" dans le projet 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
la source
1
Merci pour cette suggestion alternative. Ce n'était pas ce que je cherchais, mais s'est avéré être une solution beaucoup plus simple et plus rapide pour appliquer des traductions de packages uniques sur des bibliothèques héritées qui ne changeront jamais.
Frelling
avez-vous compris la raison de ces échecs en comparant le contenu de leur sortie respective?
tribbloid
2

Je pense qu'un exemple de la nécessité d'un pot "ombré" est une fonction AWS Lambda. Ils semblent vous permettre de télécharger seulement 1 pot, pas une collection entière de .jars comme vous le trouveriez dans un fichier .war typique. Ainsi, la création d'un seul .jar avec toutes les dépendances du projet vous permet de le faire.

atom88
la source