Wofür wird das Maven-Shade-Plugin verwendet und warum sollten Sie Java-Pakete verschieben?

281

Ich habe das Maven-Shade-Plugin gefunden, das in der pom.xml von jemandem verwendet wird. Ich habe noch nie ein Maven-Shade-Plugin verwendet (und ich bin ein Maven n00b), also habe ich versucht, den Grund für die Verwendung und die Funktionsweise zu verstehen.

Ich habe mir die Maven-Dokumente angesehen , kann diese Aussage jedoch nicht verstehen:

"Dieses Plugin bietet die Möglichkeit, das Artefakt einschließlich seiner Abhängigkeiten in ein Uber-JAR zu packen und die Pakete einiger Abhängigkeiten zu schattieren - dh umzubenennen."

Die Dokumentation auf der Seite scheint nicht sehr für Neulinge geeignet zu sein.

Was ist ein "Überglas"? Warum sollte jemand einen machen wollen? Was bringt es, die Pakete der Abhängigkeiten umzubenennen? Ich habe versucht, die Beispiele auf der Apache-Seite des Maven-Shade-Plugins wie "Auswählen von Inhalten für Uber Jar" durchzugehen, aber ich kann immer noch nicht verstehen, was mit "Shading" erreicht wird.

Hinweise auf veranschaulichende Beispiele / Anwendungsfälle (mit einer Erklärung, warum in diesem Fall eine Schattierung erforderlich war - welches Problem wird gelöst) sind willkommen. Wann sollte ich das Maven-Shade-Plugin verwenden?

Nichtsein
quelle
16
Für die Benennung "uber jar" habe ich die einzige Assoziation mit Deutsch, wobei "über" "über" bedeutet. Zusammen bedeutet es wörtlich "Glas über alle anderen Gläser". "Shading" ist dasselbe wie "Package Reallocation", das für kollidierende Klassen oder Ressourcen benötigt wird.
dma_k
1
s/reallocation/relocation/im obigen Kommentar.
Christopher Schultz
12
Das Überglas ist genau wie der eine Ring: Ein Glas, um sie alle zu regieren, ein Glas, um sie zu finden, ein Glas, um sie alle zu bringen und sie in der Dunkelheit zu binden.
Marti Nito

Antworten:

342

Kurz gesagt, Uber JAR ist ein JAR, das alles enthält.

Normalerweise verlassen wir uns in Maven auf das Abhängigkeitsmanagement. Ein Artefakt enthält nur die Klassen / Ressourcen von sich. Maven ist dafür verantwortlich, alle Artefakte (JARs usw.) des Projekts herauszufinden, je nachdem, wann das Projekt erstellt wird.

Ein Uber-Jar ist etwas, das alle Abhängigkeiten aufnimmt, den Inhalt der Abhängigkeiten extrahiert und sie mit den Klassen / Ressourcen des Projekts selbst in einer großen JAR zusammenfasst. Mit einem solchen Uber-Jar ist die Ausführung einfach, da Sie nur eine große JAR anstelle von Tonnen kleiner JARs benötigen, um Ihre App auszuführen. In einigen Fällen erleichtert es auch die Verteilung.

Nur eine Randnotiz. Vermeiden Sie die Verwendung von Uber-Jar als Maven-Abhängigkeit, da dies die Funktion zur Auflösung von Abhängigkeiten von Maven ruiniert. Normalerweise erstellen wir uber-jar nur für das endgültige Artefakt für die tatsächliche Bereitstellung oder für die manuelle Verteilung, nicht jedoch für das Maven-Repository.


Update: Ich habe gerade festgestellt, dass ich einen Teil der Frage nicht beantwortet habe: "Was bringt es, die Pakete der Abhängigkeiten umzubenennen?". Hier sind einige kurze Updates, die hoffentlich Menschen mit ähnlichen Fragen helfen werden.

Das Erstellen von Uber-Jar für eine einfache Bereitstellung ist ein Anwendungsfall des Shadow-Plugins. Es gibt auch andere häufige Anwendungsfälle, bei denen Pakete umbenannt werden.

Zum Beispiel entwickle ich eine FooBibliothek, die von einer bestimmten Version (z. B. 1.0) der BarBibliothek abhängt . Angenommen, ich kann keine andere Version von Barlib verwenden (aufgrund von API-Änderungen oder anderen technischen Problemen usw.). Wenn ich einfach erklären , Bar:1.0wie Foo‚s Abhängigkeit in Maven ist es möglich , ein Problem zu fallen: Ein QuxProjekt wird in Abhängigkeit von Foo, und auch Bar:2.0(und es kann nicht verwendet werden, Bar:1.0weil QuxBedürfnisse in neue Funktion nutzen zu können Bar:2.0). Hier ist das Dilemma: sollte Quxverwenden Bar:1.0(welcher QuxCode wird nicht funktionieren) oder Bar:2.0(welcher FooCode wird nicht funktionieren)?

Um dieses Problem zu lösen, kann der Entwickler von Foodas Schatten-Plugin verwenden, um seine Verwendung von umzubenennen Bar, sodass alle Klassen in Bar:1.0jar in jar eingebettet Foosind und das Paket der eingebetteten BarKlassen von com.barauf geändert wird com.foo.bar. Auf diese Weise kann Quxcan sicher davon abhängen, Bar:2.0da jetzt Foonicht mehr davon abhängig Barist und eine eigene Kopie von "geändert" Barin einem anderen Paket verwendet wird.

Adrian Shum
quelle
5
Danke, das hilft sehr. Wird es "Schattierung" genannt, weil es die Abhängigkeiten und andere Gläser im Überglas verbirgt?
Nichtsein
6
Ich bin mir über den Grund des Namens nicht wirklich sicher, aber auf der Titelseite scheint es, dass die "Schattierung" das "Verstecken" von Abhängigkeiten beschreibt. Da Sie optional einige Abhängigkeiten in die schattierte JAR aufnehmen können, generiert das Schatten-Plugin auch einen korrekten POM für Sie, der die enthaltenen Abhängigkeiten entfernt. Es scheint, dass Schattierung diesen Prozess beschreibt
Adrian Shum
1
@AdrianShum: Können Sie vorschlagen, wie Sie ein Überglas erstellen, ohne das Schatten-Plugin zu verwenden? Oder speziell, wie das "Ruinieren der Abhängigkeitsauflösungsfunktion von Maven" behoben werden kann?
Suraj Menon
3
@ SurajMenon: Die Verwendung des Shade-Plugins ist der einfachste Weg, um ein Überglas zu erstellen. Sie können jedoch auch das Assembly-Plugin und den integrierten jar-with-dependencyDeskriptor verwenden. Für das Problem der Abhängigkeits-Roslution mit Uber-Jar habe ich bereits in meiner Antwort erwähnt: Verwenden Sie Uber-Jar nicht als Abhängigkeit, Punkt. Ein bisschen detaillierter: Bevor Sie das Uber-Jar erstellen, sollten Sie ein normales Projekt mit normaler Abhängigkeit haben. Dieses ursprüngliche Artefakt ist dasjenige, das Sie als Abhängigkeit verwenden sollten (anstelle des Überglases)
Adrian Shum
1
Nur eine kleine Frage: Hört es auf zu Class.forNamearbeiten?
SOFe
64

Ich habe mich kürzlich gefragt, warum elasticsearch einige (aber nicht alle) seiner Abhängigkeiten schattiert und verlagert. Hier ist eine Erklärung des Projektbetreuers @kimchy :

Der Schattierungsteil ist beabsichtigt, die schattierten Bibliotheken, die wir in Elasticsearch verwenden, sind für alle Absichten und Zwecke Teil von Elasticsearch. Die verwendete Version ist eng mit dem verknüpft, was Elasticsearch verfügbar macht und wie es die Bibliothek verwendet, basierend auf den Interna der Funktionsweise der Bibliothek (und das wechselt zwischen den Versionen), Netty und Guave sind gute Beispiele.

Übrigens habe ich kein Problem damit, tatsächlich mehrere Gläser mit Elasticsearch bereitzustellen, eines mit nicht schattiertem Lucene und eines mit schattiertem Lucene. Ich bin mir nicht sicher, wie ich es mit Maven machen soll. Ich möchte keine Version bereitstellen, die zum Beispiel Netty / Jackson nicht schattiert, da Elasticsearch eine sehr enge Nutzung mit sich bringt (z. B. die bevorstehende Verbesserung des Pufferrings mit einer früheren Version von Netty mit Ausnahme der aktuellen Version verbrauchen tatsächlich mehr Speicher als erheblich weniger).

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

Und noch eine hier von drawr :

Die Schattierung ist wichtig, um unsere Abhängigkeiten (insbesondere Netty, Lucene, Guave) nahe an unserem Code zu halten, damit wir ein Problem beheben können, selbst wenn der Upstream-Anbieter zurückbleibt. Es ist möglich, dass wir modularisierte Versionen des Codes verteilen, die bei Ihrem speziellen Problem helfen würden (z. B. # 2091), aber wir können die schattierten Abhängigkeiten derzeit nicht einfach entfernen. Sie können eine lokale Version von ES für Ihre Zwecke erstellen, bis es eine bessere Lösung gibt.

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

Das ist also ein Anwendungsfall. Als anschauliches Beispiel sehen Sie unten, wie das Maven-Shade-Plugin in der pom.xml (v0.90.5) von elasticsearch verwendet wird. In den artifactSet::includeZeilen wird angegeben, welche Abhängigkeiten in die über JAR gezogen werden sollen (im Grunde werden sie entpackt und zusammen mit den eigenen Klassen von elasticsearch neu gepackt, wenn das Ziel-Elasticsearch-JAR erstellt wird. (Falls Sie dies noch nicht wussten, handelt es sich um eine JAR-Datei Nur eine ZIP-Datei, die die Klassen, Ressourcen usw. des Programms und einige Metadaten enthält. Sie können eine extrahieren, um zu sehen, wie sie zusammengesetzt ist.)

Die relocations::relocationZeilen sind ähnlich, außer dass sie in jedem Fall auch die angegebenen Ersetzungen auf die Klassen der Abhängigkeit anwenden - in diesem Fall bringen sie sie unter org.elasticsearch.common.

Schließlich filtersschließt der Abschnitt einige Dinge aus der Ziel-JAR aus, die nicht vorhanden sein sollten - wie JAR-Metadaten, Ant-Build-Dateien, Textdateien usw., die mit einigen Abhängigkeiten gepackt sind, aber nicht zu einer übergeordneten JAR gehören.

<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
quelle
2

Kleine Warnung

Obwohl das nicht beschreibt, warum man das Maven-Shade-Plugin verwenden möchte (da die ausgewählte Antwort es ziemlich gut beschreibt), möchte ich darauf hinweisen, dass ich Probleme damit hatte. Es hat die JAR geändert (seitdem das, was es tut) und es hat eine Regression in meiner Software verursacht.

Anstatt dieses (oder das Maven-Jarjar-Plugin) zu verwenden, habe ich JarJars Binärdatei verwendet, die problemlos zu funktionieren scheint.

Ich poste hier meine Lösung, da ich einige Zeit gebraucht habe, um eine anständige Lösung zu finden.


Laden Sie die JAR-Datei von JarJar herunter

Sie können das Glas hier herunterladen: https://code.google.com/p/jarjar/ Im linken Menü haben Sie einen Link zum Herunterladen.


Verwendung von JarJar zum Verschieben von Klassen einer JAR von einem Paket in ein anderes

In diesem Beispiel ändern wir das Paket von "com.fasterxml.jackson" in "io.kuku.dependencies.com.fasterxml.jackson". - Die Quell-JAR heißt "jackson-database-2.6.4.jar" und die neue modifizierte (Ziel-) JAR heißt "kuku-jackson-database-2.6.4.jar". - Die JAR-Datei "jarjar" befindet sich in Version 1.4

  1. Erstellen Sie eine "rules.txt" -Datei. Der Inhalt der Datei sollte sein (beachten Sie den Punkt vor dem '@'): Regel com.fasterxml.jackson. ** io.kuku.dependencies.com.fasterxml.jackson. @ 1

  2. Führen Sie den folgenden Befehl aus: java -jar jarjar-1.4.jar process rules.txt jackson-database-2.6.4.jar kuku-jackson-database-2.6.4.jar


Installieren der geänderten JARs im lokalen Repository

In diesem Fall installiere ich 3 Dateien im Ordner "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 - Verpackung = Glas

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 - Verpackung = Glas

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


Verwenden der geänderten JARs im POM des Projekts

In diesem Beispiel ist dies das Element "Abhängigkeiten" im Projekt 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
quelle
1
Vielen Dank für diesen alternativen Vorschlag. Dies war nicht das, wonach ich gesucht hatte, aber es stellte sich als viel einfachere und schnellere Lösung heraus, um einmalige Paketübersetzungen auf ältere Bibliotheken anzuwenden, die sich nie ändern werden.
Frelling
Haben Sie den Grund für solche Fehler herausgefunden, indem Sie den Inhalt ihrer jeweiligen Ausgabe verglichen haben?
Tribbloid
2

Ich denke, ein Beispiel für die Notwendigkeit eines "schattierten" Glases ist eine AWS Lambda-Funktion. Sie scheinen nur 1 Glas hochladen zu lassen, nicht eine ganze Sammlung von .jars, wie Sie sie in einer typischen .war-Datei finden würden. Wenn Sie also eine einzelne JAR-Datei mit allen Abhängigkeiten des Projekts erstellen, können Sie dies tun.

atom88
quelle