Что такое «затененная» зависимость Java?

76

Разработчик JVM здесь. В последнее время я видел подшучивание в чатах IRC и даже в моем собственном офисе о так называемых « затененных » библиотеках Java. Контекст использования будет примерно таким:

« Такой и так предоставляет« затененный »клиент для XYZ ».

Прекрасным примером является проблема Jira для HBase : « Публикация клиентского артефакта с затененными зависимостями »

Поэтому я спрашиваю: что такое затененный JAR, что значит быть «затененным»?

smeeb
источник

Ответы:

88

Затенение зависимостей - это процесс включения и переименования зависимостей (таким образом, перемещение классов и перезапись затронутых байт-кода и ресурсов) для создания частной копии, которую вы связываете вместе со своим собственным кодом .

Это понятие обычно ассоциируется с uber-jar (или толстыми банками ).

В этом термине есть некоторая путаница из-за плагина maven shade, который под этим именем делает две вещи (цитируя свою собственную страницу):

Этот плагин предоставляет возможность упаковать артефакт в uber-jar, включая его зависимости, и затемнить - то есть переименовать - пакеты некоторых из зависимостей.

Таким образом, часть затенения на самом деле необязательна: плагин позволяет включать зависимости в ваш jar (fat jar) и, опционально, переименовывать (shade) зависимости .

Добавление другого источника :

Затенить библиотеку означает взять файлы содержимого указанной библиотеки, поместить их в свой собственный jar-файл и изменить их пакет . Это отличается от упаковки, которая просто отправляет файлы библиотек вместе с вашим собственным jar, не перемещая их в другой пакет.

Технически говоря, зависимости затенены. Но принято называть fat-jar-with-shaded-зависимости "затененным jar", и если этот jar является клиентом для другой системы, его можно назвать "затененным клиентом".

Вот название проблемы Jira для HBase, которую вы связали в своем вопросе:

Опубликовать клиентский артефакт с затененными зависимостями

Поэтому в этом посте я пытаюсь представить 2 понятия, не объединяя их.

Добро

Uber-jar часто используются для доставки приложения в виде одного файла (облегчает развертывание и запуск). Они также могут использоваться для доставки библиотек вместе с некоторыми (или всеми) затененными их зависимостями , чтобы избежать конфликтов при использовании другими приложениями (которые могут использовать разные версии этих библиотек).

Существует несколько способов создания Uber-jar-файлов, но maven-shade-pluginс функцией перемещения классов идет еще дальше :

Если UAR JAR повторно используется в качестве зависимости какого-либо другого проекта, непосредственное включение классов из зависимостей артефакта в UAR JAR может вызвать конфликты загрузки классов из-за дублирования классов на пути к классам. Чтобы решить эту проблему, можно переместить классы, которые включаются в затененный артефакт, чтобы создать личную копию их байт-кода.

(Историческая справка: Jar Jar Links предлагал эту функцию перемещения раньше)

Таким образом, благодаря этому вы можете сделать зависимости библиотек деталями реализации , если только вы не выставляете классы из этих библиотек в своем API.

Допустим, у меня есть проект ACME Quantanizer ™, который предоставляет DecayingSyncQuantanizerкласс и зависит от Apache commons-rng (потому что, конечно, для правильного квантования вам нужен XorShift1024Star, да).

Если я использую плагин Shade Maven для создания Uber-JAR, и я смотрю внутрь, я вижу эти файлы классов:

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

Теперь, если я использую функцию перемещения классов:

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

Содержание uber-jar выглядит так:

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

Это не просто переименование файлов, это перезапись байт-кода, который ссылается на перемещенные классы (поэтому мои собственные классы и классы commons-rng все преобразуются).

Кроме того, плагин Shade также генерирует новый POM ( dependency-reduced-pom.xml), в котором затененные зависимости удаляются из <dependencies>раздела. Это помогает использовать затененную банку в качестве зависимости для другого проекта. Таким образом, вы можете опубликовать эту банку вместо основной или обеих (используя квалификатор для заштрихованной банки).

Так что это может быть очень полезно ...

Плохо

... но это также создает ряд проблем. Объединение всех зависимостей в единое «пространство имен» внутри фляги может стать грязным и потребовать затенения и работы с ресурсами.

Например: как обращаться с файлами ресурсов, которые включают имена классов или пакетов? Файлы ресурсов, такие как дескрипторы поставщика услуг, под которыми все живут META-INF/services?

Плагин Shade предлагает преобразователи ресурсов, которые могут помочь с этим:

Объединение классов / ресурсов из нескольких артефактов в один UAR-файл JAR является прямым, если нет перекрытия. В противном случае требуется некоторая логика для объединения ресурсов из нескольких JAR-файлов. Это где трансформеры ресурса начинают действовать.

Но это все еще грязно, и проблемы почти невозможно предвидеть (довольно часто вы обнаруживаете проблемы трудным путем в производстве). Посмотрите, почему мы остановили строительство банок с жиром .

В общем, развертывание толстой фляги как отдельного приложения / службы все еще очень распространено, вам просто нужно знать о проблемах, а для некоторых из них вам могут понадобиться затенение или другие приемы.

Гадкий

Есть много более сложных вопросов (отладка, тестируемость, совместимость с OSGi и экзотическими загрузчиками классов ...).

Но что более важно, когда вы создаете библиотеку, различные проблемы, которые, как вы думали, вы могли бы контролировать, теперь становятся бесконечно более сложными, потому что ваш jar будет использоваться во многих различных контекстах (в отличие от толстого jar, который вы развертываете как отдельное приложение / сервис). в контролируемой среде).

Например, ElasticSearch раньше затенял некоторые зависимости в поставляемых ими банках, но они решили прекратить делать это :

До версии 2.0 Elasticsearch предоставлялся в виде JAR с некоторыми (но не всеми) общими зависимостями, затененными и упакованными в один и тот же артефакт. Это помогло пользователям Java, которые внедрили Elasticsearch в свои собственные приложения, чтобы избежать конфликтов версий модулей, таких как Guava, Joda, Jackson и т. Д. Конечно, все еще был список других незатененных зависимостей, таких как Lucene, которые все еще могут вызывать конфликты.
К сожалению, затенение является сложным и подверженным ошибкам процессом, который решает проблемы для одних людей, а создает проблемы для других. Затенение очень мешает разработчикам и авторам плагинов правильно писать и отлаживать код, потому что пакеты переименовываются во время сборки. Наконец, мы тестировали Elasticsearch без тени, затем отправляли заштрихованную банку, и нам не нравится отправлять то, что мы не проверяем.
Мы решили отгрузить Elasticsearch без затенения начиная с 2.0.

Обратите внимание, что они также относятся к затененным зависимостям , а не к затененным банкам

Хьюг М.
источник
1
Спасибо, что нашли время, чтобы объяснить это. Официальная документация плагина maven shade совершенно неадекватна и не обсуждает ничего из этого или даже не дает определения «uber jar». Эта документация тупая и бесполезная. Ваша рецензия полезна.
Cheeso
Отличное объяснение, я думаю, это должно быть включено в официальные документы
Аделин
7

Позвольте мне ответить на вопрос с помощью программного обеспечения, которое отвечает за создание затененных банок ... по крайней мере, при использовании maven.

Взято с домашней страницы плагина Apache Maven Shade :

Этот плагин предоставляет возможность упаковать артефакт в uber-jar, включая его зависимости, и затемнить - то есть переименовать - пакеты некоторых из зависимостей.

Затененный jar aka uber-jar aka fat jar по умолчанию будет содержать все зависимости, которые требуются для запуска приложения Java, поэтому в пути к классу не требуется никаких дополнительных зависимостей. Вам нужна только правильная версия Java для запуска вашего приложения. Затененный jar поможет избежать проблем развертывания / пути к классам, но он будет намного больше, чем исходный jar приложения, и не поможет вам избежать ада jar.

Йеско Р.
источник
1
Опасаюсь, что этот ответ неполон: он объясняет, что такое баночки с жиром / убером, но не объясняет затенение . И да, затенение на 100% должно помочь с «чертовым адом» (что делает последнюю часть этого ответа неправильной). Так что это полезно на некотором уровне, но добавляет путаницы: - /
Hugues M.
1
@HuguesMoreau Я, возможно, не был на 100% полон в своем ответе, но это все еще принесло точку, которую я хотел высказать. Спасибо, что принесли недостающую часть на стол. Затенение не избежит ужаса, это то, что я имел в виду и написал, но оно даст вам некоторые инструменты под рукой, которые позволят вам решить некоторые из его проблем, но не автоматические. Что делает последнюю часть, если читать и интерпретировать так, как я это имел в виду, по крайней мере, хорошо. :)
Jesko R.