Co to jest „zacieniona” zależność Java?

74

Deweloper JVM tutaj. Ostatnio widziałem żarty na czatach IRC, a nawet w moim biurze na temat tak zwanych „ zacienionych ” bibliotek Java. Kontekst użycia będzie taki jak:

Takie, a więc zapewnia„ zacienionego ”klienta dla XYZ.

Doskonałym przykładem jest ten problem Jira dla HBase : „ Opublikuj artefakt klienta z zacienionymi zależnościami

Pytam więc: co to jest zacieniony JAR, co to znaczy być „zacienionym”?

smeeb
źródło

Odpowiedzi:

86

Zależności cieniowania to proces włączania i zmieniania nazw zależności (przenoszenie klas i przepisywanie pod wpływem kodu i zasobów) w celu utworzenia prywatnej kopii, którą pakujesz razem z własnym kodem .

Pojęcie to zwykle kojarzy się z słoikami Uber-Jars (zwanymi także słoikami tłuszczowymi ).

Istnieje pewne zamieszanie w związku z tym terminem , ze względu na wtyczkę cienia maven, która pod tą pojedynczą nazwą robi 2 rzeczy (cytowanie własnej strony):

Ta wtyczka zapewnia możliwość spakowania artefaktu w słoik uber-jar, w tym jego zależności oraz przyciemnienia - tj. Zmiany nazwy - pakietów niektórych zależności.

Tak więc część cieniująca jest w rzeczywistości opcjonalna: wtyczka pozwala uwzględniać zależności w słoiku (gruby słoik) i opcjonalnie zmieniać zależności (cień) .

Dodanie innego źródła :

Cieniowanie biblioteki polega na pobraniu plików zawartości wspomnianej biblioteki, włożeniu ich do własnego słoika i zmianie ich pakietu . Różni się to od pakowania, które po prostu wysyła pliki bibliotek do własnego słoika bez przenoszenia ich do innego pakietu.

Z technicznego punktu widzenia zależności są zacienione. Ale często określa się zależności „gruby słoik-z-zacienionymi” jako „zacieniony słoik”, a jeśli ten słoik jest klientem dla innego systemu, można go nazwać „zacienionym klientem”.

Oto tytuł problemu Jira dla HBase, który podałeś w swoim pytaniu:

Opublikuj artefakt klienta z zacienionymi zależnościami

Więc w tym poście staram się przedstawić 2 koncepcje bez ich łączenia.

Dobry

Słoiki Uber-jars są często używane do wysyłania aplikacji jako pojedynczego pliku (ułatwia wdrożenie i uruchomienie). Można ich także używać do wysyłania bibliotek wraz z niektórymi (lub wszystkimi) ich zależnościami, które są zacienione , aby uniknąć konfliktów, gdy są używane przez inne aplikacje (które mogą korzystać z różnych wersji tych bibliotek).

Istnieje kilka sposobów budowania słoików Uber-Jars, ale maven-shade-pluginidzie o krok dalej dzięki funkcji przenoszenia klas :

Jeśli plik JAR uber zostanie ponownie wykorzystany jako zależność innego projektu, bezpośrednie włączenie klas z zależności artefaktu do pliku JAR uber może powodować konflikty ładowania klas z powodu duplikacji klas na ścieżce klasy. Aby rozwiązać ten problem, można przenieść klasy zawarte w zacienionym artefakcie, aby utworzyć prywatną kopię ich kodu bajtowego.

(Uwaga historyczna: Jar Jar Links wcześniej oferował tę funkcję relokacji)

Dzięki temu możesz uczynić zależności biblioteki szczegółowymi implementacjami , chyba że udostępnisz klasy z tych bibliotek w interfejsie API.

Powiedzmy, że mam projekt, ACME Quantanizer ™, który zapewnia DecayingSyncQuantanizerklasę i zależy od commons-rng Apache ( oczywiście, aby właściwie skwantyzować, potrzebujesz XorShift1024Starduh).

Jeśli użyję wtyczki shadow maven do wytworzenia słoika i zajrzę do środka, zobaczę te pliki klas:

com/acme/DecayingSyncQuantanizer.class
org/apache/commons/rng/RandomProviderState.class
org/apache/commons/rng/RestorableUniformRandomProvider.class
...
org/apache/commons/rng/core/source64/XorShift1024Star.class
org/apache/commons/rng/core/util/NumberFactory.class

Teraz, jeśli użyję funkcji przenoszenia klasy:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.0.0</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <relocations>
          <relocation>
            <pattern>org.apache.commons</pattern>
            <shadedPattern>com.acme.shaded.apachecommons</shadedPattern>
          </relocation>
        </relocations>
      </configuration>
    </execution>
  </executions>
</plugin>

Zawartość słoika wygląda następująco:

com/acme/DecayingSyncQuantanizer.class
com/acme/shaded/apachecommons/rng/RandomProviderState.class
com/acme/shaded/apachecommons/rng/RestorableUniformRandomProvider.class
...
com/acme/shaded/apachecommons/rng/core/source64/XorShift1024Star.class
com/acme/shaded/apachecommons/rng/core/util/NumberFactory.class

To nie tylko zmiana nazw plików, przepisuje kod bajtowy, który odwołuje się do przeniesionych klas (więc moje własne klasy i klasy wspólne są przekształcane).

Ponadto wtyczka Shade wygeneruje również nowy POM ( dependency-reduced-pom.xml), w którym zależności zacienione zostaną usunięte z <dependencies>sekcji. Pomaga to wykorzystać zacieniony słoik jako zależność dla innego projektu. Możesz więc opublikować ten słoik zamiast podstawowego lub oba (używając kwalifikatora dla zacienionego słoika).

To może być bardzo przydatne ...

Źli

... ale wiąże się to również z wieloma problemami. Agregowanie wszystkich zależności w jedną „przestrzeń nazw” w słoiku może powodować bałagan i wymagać cieniowania i bałaganu przy użyciu zasobów.

Na przykład: jak postępować z plikami zasobów, które zawierają nazwy klas lub pakietów? Pliki zasobów, takie jak deskryptory usługodawców, pod którymi wszystkie żyją META-INF/services?

Wtyczka cieniowania oferuje transformatory zasobów, które mogą w tym pomóc:

Agregowanie klas / zasobów z kilku artefaktów w jeden plik JAR Uber jest proste, o ile nie zachodzi na siebie nakładanie się. W przeciwnym razie wymagana jest logika do łączenia zasobów z kilku plików JAR. Tutaj rozpoczynają się transformatory zasobów .

Ale wciąż jest bałagan, a problemy są prawie niemożliwe do przewidzenia (dość często problemy odkrywa się na etapie produkcji). Zobacz, dlaczego przestaliśmy budować słoiki z tłuszczem .

Podsumowując, wdrażanie grubego słoika jako samodzielnej aplikacji / usługi jest nadal bardzo powszechne, musisz tylko wiedzieć o problemach, a dla niektórych z nich możesz potrzebować cieniowania lub innych sztuczek.

Brzydki

Istnieje wiele trudniejszych problemów (debugowanie, testowalność, kompatybilność z OSGi i egzotycznymi modułami ładującymi ...).

Ale co ważniejsze, kiedy tworzysz bibliotekę, różne problemy, które Twoim zdaniem można kontrolować, stają się teraz nieskończenie bardziej skomplikowane, ponieważ słoik będzie używany w wielu różnych kontekstach (w przeciwieństwie do grubego słoika, który wdrażasz jako samodzielną aplikację / usługę w kontrolowanym środowisku).

Na przykład ElasticSearch zwykł zaciemniać niektóre zależności w słoikach, które wysyłali, ale postanowili przestać to robić :

Przed wersją 2.0 Elasticsearch był dostarczany jako plik JAR z pewnymi (ale nie wszystkimi) typowymi zależnościami zacienionymi i spakowanymi w tym samym artefakcie. Pomogło to użytkownikom Java, którzy osadzają Elasticsearch we własnych aplikacjach, uniknąć konfliktów wersji modułów takich jak Guava, Joda, Jackson itp. Oczywiście nadal istniała lista innych niecieniowanych zależności, takich jak Lucene, które nadal mogą powodować konflikty.
Niestety, cieniowanie jest złożonym i podatnym na błędy procesem, który rozwiązał problemy dla niektórych osób, a jednocześnie stworzył problemy dla innych. Cieniowanie utrudnia programistom i autorom wtyczek prawidłowe pisanie i debugowanie kodu, ponieważ podczas kompilacji zmienia się nazwy pakietów. W końcu testowaliśmy Elasticsearch bez cieniowania, a następnie wysyłaliśmy zacieniony słoik i nie lubimy niczego, czego nie testujemy.
Zdecydowaliśmy się wysłać Elasticsearch bez cieniowania od wersji 2.0.

Pamiętaj, że one również odnoszą się do zacienionych zależności , a nie zacienionego słoika

Hugues M.
źródło
1
Dziękujemy za poświęcenie czasu na wyjaśnienie tego. Oficjalna dokumentacja wtyczki cienia maven jest całkowicie nieodpowiednia i nie omawia żadnej z tych kwestii, ani nawet nie zadaje sobie trudu zdefiniowania „słoika uber”. Ta dokumentacja jest tępa i bezużyteczna. Twój opis jest przydatny.
Cheeso
Doskonałe wyjaśnienie, myślę, że powinno to zostać uwzględnione w oficjalnych dokumentach
Adelin
7

Pozwól mi odpowiedzieć na pytanie za pomocą oprogramowania odpowiedzialnego za tworzenie zacienionych słoików ... przynajmniej podczas korzystania z maven.

Zaczerpnięte ze strony głównej Apache Maven Shade Plugin :

Ta wtyczka zapewnia możliwość spakowania artefaktu w słoik uber-jar, w tym jego zależności oraz przyciemnienia - tj. Zmiany nazwy - pakietów niektórych zależności.

Zacieniony słoik, czyli słoik uber-jar, zwany słoikiem tłuszczu, domyślnie będzie zawierał każdą zależność wymaganą do uruchomienia aplikacji Java, więc żadna dodatkowa zależność nie będzie wymagana w ścieżce klas. Do uruchomienia aplikacji potrzebujesz tylko poprawnej wersji Java. Zacieniony słoik pomoże uniknąć problemów z wdrażaniem / ścieżką klas, ale będzie znacznie większy niż oryginalny słoik aplikacji i nie pomoże uniknąć piekła.

Jesko R.
źródło
1
Obawiam się, że ta odpowiedź jest niepełna: wyjaśnia, czym są słoiki z tłuszczem / uberem, ale nie wyjaśnia części zacieniającej . I tak, cieniowanie ma w 100% pomóc w „piekle jar” (co sprawia, że ​​ostatnia część tej odpowiedzi jest niepoprawna). Jest to przydatne na pewnym poziomie, ale dodaje zamieszanie: - /
Hugues M.
1
@HuguesMoreau Być może nie jestem w 100% kompletny w mojej odpowiedzi, ale nadal przyniosło to punkt, który chciałem naprawić. Dzięki za dostarczenie brakującej części na stół. Cieniowanie nie pozwoli uniknąć piekła, to właśnie miałem na myśli i napisałem, ale da ci kilka dostępnych narzędzi, które pozwolą ci rozwiązać niektóre z jego problemów, ale nie jest to automatyczne. Co czyni ostatnią część, jeśli przeczytałem i zinterpretowałem tak, jak chciałem, przynajmniej w porządku. :)
Jesko R.