Do czego służy wtyczka maven-shadow-plugin i dlaczego chcesz przenieść pakiety Java?


282

Znalazłem wtyczkę maven-shadow-plugin w czyimś pliku pom.xml. Nigdy wcześniej nie korzystałem z wtyczki maven-shadow-plugin (i jestem Maven n00b), więc próbowałem zrozumieć powód używania tego i co on robi.

Spojrzałem na dokumenty Maven , jednak nie rozumiem tego stwierdzenia:

„Ta wtyczka umożliwia pakowanie artefaktu w słoik uber-jar, w tym jego zależności, oraz przyciemnianie - tj. Zmianę nazwy - pakietów niektórych zależności”.

Dokumentacja na stronie nie wydaje się zbyt przyjazna dla początkujących.

Co to jest „słoik uber”? Dlaczego ktoś miałby chcieć to zrobić? Po co zmieniać nazwy pakietów zależności? Próbowałem przejść przez przykłady na stronie apache plugin maven-shadow-plugin, takie jak „Wybieranie zawartości dla Uber Jar”, ale nadal nie rozumiem, co można osiągnąć dzięki „cieniowaniu”.

Docenione zostaną wszelkie wskazówki do przykładowych przykładów / przypadków użycia (z wyjaśnieniem, dlaczego w tym przypadku wymagane było cieniowanie - jaki problem rozwiązuje). Wreszcie, kiedy powinienem użyć wtyczki maven-shade-plug?


16
Dla nazwy „słoik uber” mam jedyne skojarzenie z niemieckim, gdzie „über” oznacza „ponad”. Razem dosłownie oznacza to „jar-over-all-other-jar”. „Cieniowanie” jest tym samym co „realokacja pakietu”, które jest potrzebne dla kolidujących klas lub zasobów.
dma_k,

1
s/reallocation/relocation/w powyższym komentarzu.
Christopher Schultz

12
Uber słoik jest jak jeden pierścień: jeden słoik, aby rządzić wszystkimi, jeden słoik, aby je znaleźć, jeden słoik, aby przynieść je wszystkie, a w ciemności związać je.
Marti Nito,

Odpowiedzi:


343

Krótko mówiąc, Uber JAR to JAR zawierający wszystko.

Zwykle w Maven polegamy na zarządzaniu zależnościami. Artefakt zawiera same klasy / zasoby. Maven będzie odpowiedzialna za znalezienie wszystkich artefaktów (plików JAR itp.), Które są projektami w zależności od tego, kiedy projekt został zbudowany.

Uber-jar jest czymś, co bierze wszystkie zależności i wyodrębnia zawartość zależności i umieszcza je wraz z klasami / zasobami samego projektu w jednym dużym pliku JAR. Dzięki takiemu słoikowi jest łatwy do wykonania, ponieważ do uruchomienia aplikacji potrzebujesz tylko jednego dużego pliku JAR zamiast ton małych plików JAR. W niektórych przypadkach ułatwia to również dystrybucję.

Tylko uwaga. Unikaj używania Uber-Jar jako zależności Maven, ponieważ rujnuje to funkcję rozwiązywania zależności w Maven. Zwykle uber-jar tworzymy tylko dla końcowego artefaktu do faktycznego wdrożenia lub do ręcznej dystrybucji, ale nie do umieszczenia w repozytorium Maven.


Aktualizacja: Właśnie odkryłem, że nie odpowiedziałem na jedną część pytania: „Po co zmieniać nazwy pakietów zależności?”. Oto kilka krótkich aktualizacji i mam nadzieję, że pomogą ludziom, którzy mają podobne pytanie.

Stworzenie słoika Uber-jar dla łatwego wdrożenia to jeden przypadek użycia wtyczki zaciemniającej. Istnieją również inne typowe przypadki użycia, które wymagają zmiany nazwy pakietu.

Na przykład tworzę Foobibliotekę, która zależy od konkretnej wersji (np. 1.0) Barbiblioteki. Zakładając, że nie mogę korzystać z innej wersji Barlib (ponieważ zmiana API lub inne problemy techniczne itp.). Gdybym po prostu zadeklarować Bar:1.0jako Foo„s uzależnienia w Maven, można wpaść w Problem: QuxProjekt jest w zależności Foo, a także Bar:2.0(i to nie można użyć Bar:1.0, ponieważ Quxpotrzeby używać nowej funkcji Bar:2.0). Oto dylemat: czy należy Quxużyć Bar:1.0(który Quxkod nie zadziała) lub Bar:2.0(który Fookod nie zadziała)?

Aby rozwiązać ten problem, programista Foomoże zdecydować się na użycie wtyczki cieniowania, aby zmienić nazwę jego użycia Bar, tak aby wszystkie klasy w Bar:1.0jar były osadzone w Foojar, a pakiet Barklas osadzonych został zmieniony z com.barna com.foo.bar. Robiąc to, Quxmoże bezpiecznie zależeć, Bar:2.0ponieważ teraz Foonie jest już zależny Bar, i używa własnej kopii „zmienionej” Barznajdującej się w innym pakiecie.


5
Dzięki, to bardzo pomaga. Czy nazywa się to „cieniowaniem”, ponieważ ukrywa zależności i inne słoiki wewnątrz słoika?
nonbeing

6
Nie jestem do końca pewien, dlaczego tak się nazywa, ale na pierwszej stronie wydaje się sugerować, że „cieniowanie” opisuje „ukrywanie się” zależności. Ponieważ możesz opcjonalnie uwzględnić pewne zależności w zacienionym pliku JAR, wtyczka cieni wygeneruje również poprawną POM, która usunie zawarte w niej zależności. Wygląda na to, że cieniowanie opisuje ten proces
Adrian Shum,

1
@AdrianShum: Czy możesz zasugerować, jak stworzyć słoik Uber bez korzystania z wtyczki cieni? A konkretnie, jak rozwiązać „zrujnowanie funkcji rozwiązywania zależności w Maven”?
Suraj Menon,

3
@SurajMenon: Korzystanie z wtyczki Shade jest najprostszym sposobem na utworzenie słoika. Możesz jednak również użyć wtyczki asemblera i użyć wbudowanego jar-with-dependencydeskryptora. W przypadku problemu uzaleŜnienia od uzaleŜnienia za pomocą słoika uber-jar już wspomniałem w mojej odpowiedzi: Nie używaj słoika uber-jar jako zależności, kropka. Trochę bardziej szczegółowo: przed utworzeniem słoika uber powinieneś mieć normalny projekt z normalną zależnością. Ten oryginalny artefakt jest tym, którego należy użyć jako zależności (zamiast uber-jar)
Adrian Shum,

1
Tylko drobne pytanie: czy przestaje Class.forNamedziałać?
SOFe

64

Zastanawiałem się ostatnio, dlaczego elasticsearch cienia i przenosi kilka (ale nie wszystkie) z jego zależności. Oto wyjaśnienie od opiekuna projektu, @kimchy :

Część cieniująca jest celowa, zacienione biblioteki, których używamy w elasticsearch, są przeznaczone do wszystkich celów elasticsearch, zastosowana wersja jest ściśle powiązana z tym, co ujawnia elasticsearch i jak korzysta z biblioteki w oparciu o wewnętrzne funkcje biblioteki (i że zmiany między wersjami), netty i guava są świetnymi przykładami.

Przy okazji, nie mam problemu z zapewnieniem kilku słoików z elastycznymi badaniami, jeden z lucenem nie zacieniowanym, a drugi z Lucene zacieniowanym. Nie jestem jednak pewien, jak to zrobić za pomocą maven. Nie chcę na przykład oferować wersji, która nie powoduje cieniowania Netty / Jacksona, ze względu na głębokie intymne użycie, które ma wraz z nimi elasticsearch (na przykład za pomocą nadchodzącego ulepszenia buforowania w dowolnej poprzedniej wersji Netty, z wyjątkiem obecnej faktycznie zużywają więcej pamięci w porównaniu do używania znacznie mniej).

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

I kolejny tutaj od Drawr :

Cieniowanie jest ważne, aby nasze zależności (zwłaszcza netty, lucene, guava) były blisko naszego kodu, abyśmy mogli rozwiązać problem, nawet jeśli dostawca usług nadrzędnych pozostaje w tyle. Możliwe, że będziemy rozpowszechniać zmodularyzowane wersje kodu, które pomogłyby w rozwiązaniu konkretnego problemu (na przykład # 2091), ale obecnie nie możemy po prostu usunąć zacienionych zależności. Możesz zbudować lokalną wersję ES do swoich celów, dopóki nie będzie lepszego rozwiązania.

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

To jest jeden przypadek użycia. Przykładem ilustrującym jest użycie wtyczki maven-shadow-plugin w pom.xml elasticsearch (v0.90.5). Te artifactSet::includelinie instrukcji co co Zależności pociągnąć do słoika uber (w zasadzie, są one rozpakowane i i re-zapakowanego obok klas własnych elasticsearch kiedy słoik cel elasticsearch jest produkowany. (W przypadku, gdy nie wiedzą o tym już, plik JAR jest tylko plik ZIP, który zawiera klasy programu, zasoby itp. oraz niektóre metadane. Możesz go wyodrębnić, aby zobaczyć, jak jest złożony.)

Te relocations::relocationlinie są podobne, z tym że w każdym przypadku, ale także zastosować określone substytucje klas zależność jest - w tym przypadku, przynosząc je pod org.elasticsearch.common.

Wreszcie filterssekcja wyklucza niektóre elementy z docelowego pliku JAR, które nie powinny tam być - takie jak metadane JAR, pliki kompilacji mrówek, pliki tekstowe itp., Które są spakowane z pewnymi zależnościami, ale które nie należą do zwykłego pliku JAR.

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

2

Małe ostrzeżenie

Chociaż to nie opisuje, dlaczego ktoś chciałby używać wtyczki maven-shadow-plugin (ponieważ wybrana odpowiedź dość dobrze to opisuje), chciałbym zauważyć, że miałem z tym problemy. Zmieniło JAR (od tego, co robi) i spowodowało regresję w moim oprogramowaniu.

Zamiast więc używać tej (lub wtyczki maven-jarjar), użyłem pliku binarnego JarJara, który wydaje się działać bez problemu.

Zamieszczam tutaj moje rozwiązanie, ponieważ znalezienie odpowiedniego rozwiązania zajęło mi trochę czasu.


Pobierz plik JAR JarJara

Możesz pobrać słoik tutaj: https://code.google.com/p/jarjar/ W menu po lewej stronie znajduje się link do pobrania.


Jak używać JarJar do przenoszenia klas JAR z jednego pakietu do drugiego

W tym przykładzie zmienimy pakiet z „com.fasterxml.jackson” na „io.kuku.dependencies.com.fasterxml.jackson”. - Źródłowy plik JAR nosi nazwę „jackson-databind-2.6.4.jar”, ​​a nowy zmodyfikowany (docelowy) plik JAR nosi nazwę „kuku-jackson-databind-2.6.4.jar”. - Plik JAR „jarjar” znajduje się w wersji 1.4

  1. Utwórz plik „rules.txt”. Zawartość pliku powinna być (uważaj na kropkę przed znakiem „@”): reguła com.fasterxml.jackson. ** io.kuku.dependencies.com.fasterxml.jackson. @ 1

  2. Uruchom następującą komendę: java -jar jarjar-1.4.jar process rules.txt jackson-databind-2.6.4.jar kuku-jackson-databind-2.6.4.jar


Instalowanie zmodyfikowanych plików JAR w lokalnym repozytorium

W tym przypadku instaluję 3 pliki znajdujące się w folderze „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-adnotations -Dversion = 2.6.4 - Pakowanie = słoik

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 - Pakowanie = słoik

mvn install: install-file -Dfile = C: \ my-jars \ kuku-jackson-databind-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-adnotations -Dversion = 2.6.4 - Pakowanie = słoik


Używanie zmodyfikowanych plików JAR w pom pom projektu

W tym przykładzie jest to element „zależności” w projektach 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>

1
Dziękuję za tę alternatywną sugestię. Nie tego szukałem, ale okazało się, że jest to znacznie prostsze i szybsze rozwiązanie do stosowania jednorazowych tłumaczeń pakietów w starszych bibliotekach, które nigdy się nie zmienią.
Frelling

czy odkryłeś przyczynę takich awarii, porównując zawartość ich odpowiednich wyników?
tribbloid

2

Myślę, że jednym z przykładów zapotrzebowania na „zacieniony” słoik jest funkcja Lambda AWS. Wydaje się, że pozwalają tylko na przesłanie 1 słoika, a nie całej kolekcji .jars, tak jak w typowym pliku .war. Zatem utworzenie jednego pliku .jar ze wszystkimi zależnościami projektu pozwala to zrobić.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.