Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[SnowOne 2024] Константин Грибов: Вглядываясь в бездну зависимостей

[SnowOne 2024] Константин Грибов: Вглядываясь в бездну зависимостей

Современный мир разработки сложно представить себе без изобилия внешних зависимостей, особенно когда речь идёт про обширные экосистемы типа JVM. Время не стоит на месте, и раскладывание наборов jar'ников по директориям вручную и самописных систем сборки или ручной настройки в IDE ушли в прошлое, а на смену им давно подтянулись автоматизированные инструменты типа Apache Maven и Gradle. Эти системы сборки призваны значительно уменьшить рутинную часть управления зависимостями и сделать так, чтобы добавление в проект какого-нибудь open source фреймворка или библиотеки сводилось к написанию нескольких строк, а жизнь пользователя мгновенно улучшалась.

В докладе рассмотрим, как всё это выглядит со стороны мейнтейнеров open source и платформенных библиотек. И что можно сделать, если всё же необходимо сделать маленький шаг в сторону от курируемого набора библиотек в привычном фреймворке, и пришла пора долго вглядываться в бездну зависимостей.

jugnsk

May 01, 2024
Tweet

More Decks by jugnsk

Other Decks in Programming

Transcript

  1. APACHE TIKA™ Набор библиотек для извлечения данных и метаданных из

    различных файловых форматов 1000+ файловых форматов 600+ зависимостей модульность различные режимы изоляции https://tika.apache.org 2
  2. ОПИСАНИЕ ПРОБЛЕМЫ Разные пользователи — разные требования Разнообразные окружения: от минималистичных проектов

    до использования совместно с крупными фреймворками множество библиотек логирования/трассировки/сериализации/http-клиентов в экосистеме Компромисс между удобством использования в конфигурации по умолчанию и возможностью кастомизации 3
  3. ПРИМЕРЫ. БИБЛИОТЕКИ ЛОГИРОВАНИЯ Основные API SLF4J Apache Commons Logging (JCL)

    java.util.logging (JUL) Apache Log4j: 1.2.x (deprecated), 2.x JBoss Logging Реализации и бриджи Log4j 2.x и его бриджи/интеграции Logback и бриджи/интеграции SLF4J Log4j 1.2.x/reload4j JBoss Logging/LogManager Commons Logging JUL 4
  4. ПРИМЕРЫ. БИБЛИОТЕКИ ЛОГИРОВАНИЯ slf4j 1.7.x, две реализации в classpath slf4j

    2.0.x, две реализации в classpath slf4j 1.7.x, цикл SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/home/gross/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-simple/1.7.36/a41f9cfe6faafb SLF4J: Found binding in [jar:file:/home/gross/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.13/e9f345 SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory] SLF4J(W): Class path contains multiple SLF4J providers. SLF4J(W): Found provider [org.slf4j.simple.SimpleServiceProvider@776ec8df] SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@4eec7777] SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J(I): Actual provider is of type [org.slf4j.simple.SimpleServiceProvider@776ec8df] SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-reload4j.jar on the class path, preempting StackOverflowError. SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details. Exception in thread "main" java.lang.ExceptionInInitializerError at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72) at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45) at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150) at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124) at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417) at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362) at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388) at ws.gross.example.Main.<clinit>(Main.java:7) Caused by: java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound slf4j-reload4j.jar on the class path at org.slf4j.impl.Reload4jLoggerFactory.<clinit>(Reload4jLoggerFactory.java:55) ... 8 more 5
  5. ПРИМЕРЫ. РАБОТА С JSON Jackson (1.x и 2.x) JSON-P/JSON-B GSON

    реализации во фреймворках (например, Vert.X, Spray JSON в Akka) 6
  6. ПРИМЕРЫ. HTTP КЛИЕНТЫ Apache HttpComponents (4.x, 5.x) Eclipse Jetty Netty

    OkHttp реализации и обёртки во фреймворках (Vert.X, JAX-RS RestClient, Akka/Apache Pekko, Spring RestTemplate/RestClient) 7
  7. ПРОБЛЕМНЫЕ МЕСТА Прямые и транзитивные зависимости Конфигурационные файлы в JAR

    Использование shade/shadow plugin Различные механизмы реализации плагинов 8
  8. BATTERIES INCLUDED VS DIY Batteries included проще начало использования проще

    поддержка при использовании рекомендованных конфигураций часто затруднена кастомизация часто скудная документация по кастомизации Конструктор больше гибкости требует больших затрат при начале использования более дорогая поддержка 9
  9. BUILD-TIME УПРАВЛЕНИЕ ЗАВИСИМОСТЯМИ Цель: получить стабильный classpath для сборки и

    запуска приложения и его тестов Разрешение зависимостей Централизованное управления версиями Как жить в вечно меняющемся мире: изменение координат, динамические версии Опциональные и тестовые зависимости Подводные камни при использовании shade/shadow plugin 10
  10. РАЗРЕШЕНИЕ ЗАВИСИМОСТЕЙ. ИНСТРУМЕНТЫ Apache Maven Gradle Apache Ant + Apache

    Ivy Scala SBT Leiningen декларативное управление зависимостями некоторые используют исполняемую конфигурацию некоторые приняты или специализированны для определенного применения 11
  11. ТЕРМИНОЛОГИЯ Maven POM (Project Object Model) — основная единица конфигурации в Maven

    декларативно описывает модуль: его зависимости, настройку плагинов для сборки и т.п. часть конфигурации наследуется из родительской конфигурации (parent POM) всегда имеет родительскую конфигурацию (если не указана используется Super POM) 12.1
  12. ТЕРМИНОЛОГИЯ Maven POM (Project Object Model) — основная единица конфигурации в Maven

    декларативно описывает модуль: его зависимости, настройку плагинов для сборки и т.п. часть конфигурации наследуется из родительской конфигурации (parent POM) всегда имеет родительскую конфигурацию (если не указана используется Super POM) Gradle project — основная единица конфигурации в Gradle не использует наследование, общая конфигурация реализуется плагинами описывает конфигурацию в исполнимом виде (build.gradle.kts на Kotlin или build.gradle на Groovy) 12.2
  13. ТЕРМИНОЛОГИЯ Maven POM (Project Object Model) — основная единица конфигурации в Maven

    декларативно описывает модуль: его зависимости, настройку плагинов для сборки и т.п. часть конфигурации наследуется из родительской конфигурации (parent POM) всегда имеет родительскую конфигурацию (если не указана используется Super POM) Gradle project — основная единица конфигурации в Gradle не использует наследование, общая конфигурация реализуется плагинами описывает конфигурацию в исполнимом виде (build.gradle.kts на Kotlin или build.gradle на Groovy) Maven BOM (Bill Of Materials) — специальный вариант POM содержащий информацию об управлении зависимостями 12.3
  14. ТЕРМИНОЛОГИЯ Maven POM (Project Object Model) — основная единица конфигурации в Maven

    декларативно описывает модуль: его зависимости, настройку плагинов для сборки и т.п. часть конфигурации наследуется из родительской конфигурации (parent POM) всегда имеет родительскую конфигурацию (если не указана используется Super POM) Gradle project — основная единица конфигурации в Gradle не использует наследование, общая конфигурация реализуется плагинами описывает конфигурацию в исполнимом виде (build.gradle.kts на Kotlin или build.gradle на Groovy) Maven BOM (Bill Of Materials) — специальный вариант POM содержащий информацию об управлении зависимостями Gradle platform — аналог Maven BOM с расширенной моделью при публикации в репозиторий публикуется как обычный Maven BOM, так и метаданные в формате Gradle Module Metadata 12.4
  15. ОСОБЕННОСТИ MAVEN И GRADLE Maven Стратегия nearest first: по глубине

    в дереве зависимостей по порядку декларации A B D:1.1 D:1.0 A B D:1.1 C D:1.2 13
  16. ОСОБЕННОСТИ MAVEN И GRADLE Maven Стратегия nearest first: по глубине

    в дереве зависимостей по порядку декларации A B D:1.1 D:1.0 A B D:1.1 C D:1.2 Gradle Полный DAG наивысшая версия в графе зависимостей гибкое управление на всех этапах A B D:1.1 D:1.0 A B D:1.1 C D:1.2 13.1
  17. ЦЕНТРАЛИЗОВАННОЕ УПРАВЛЕНИЕ ВЕРСИЯМИ BOM (bill of materials)/platform: специальный артефакт для

    переопределения версий прямых и транзитивных зависимостей. не нужно помнить конкретную версию упрощение поддержки при обновлении возможность выравнивания версий между компонентами одной библиотеки/фреймворка более высокие требования к мейнтейнерам фреймворка/платформы  В мире Gradle также есть version catalogs (preview с 7.0, stable с 7.4), дающие пользователю возможность не указывать версии без влияния на версии транзитивных зависимостей 14
  18. Maven pom.xml 1 BOM GAV coordinates 2 Тип порождаемого артефакта,

    может также быть bom в Maven 4.0+ 3 Managed dependency GAV coordinates <project> <groupId>org.example</groupId> <artifactId>example-bom</artifactId> <version>1.1</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>example-lib</artifactId> <version>1.0</version> </dependency> </dependencies> 1 1 1 2 3 3 3 15
  19. Maven pom.xml 1 BOM GAV coordinates 2 Тип порождаемого артефакта,

    может также быть bom в Maven 4.0+ 3 Managed dependency GAV coordinates <project> <groupId>org.example</groupId> <artifactId>example-bom</artifactId> <version>1.1</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>example-lib</artifactId> <version>1.0</version> </dependency> </dependencies> 1 1 1 2 3 3 3 Gradle gradle.properties 1 BOM groupId and version settings.gradle.kts 1 BOM artifactId example-bom/build.gradle.kts 1 Managed dependency GAV coordinates group = org.example version = 1.1 1 1 include("example-bom") 1 plugins { `java-platform` } dependencies { constraints { api("org.example:example-lib:1.0") } } 1 15.1
  20. ИСПОЛЬЗОВАНИЕ BOM Maven pom.xml 1 GAV coordinates 2 Обязательное указание

    типа артефакта, т.к. по умолчанию jar 3 Специальный scope для импорта BOM <project> <dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>example-bom</artifactId> <version>1.1</version> <type>pon</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project> 1 1 1 2 3 16
  21. ИСПОЛЬЗОВАНИЕ BOM Maven pom.xml 1 GAV coordinates 2 Обязательное указание

    типа артефакта, т.к. по умолчанию jar 3 Специальный scope для импорта BOM <project> <dependencyManagement> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>example-bom</artifactId> <version>1.1</version> <type>pon</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project> 1 1 1 2 3 Gradle build.gradle.kts 1 Обычный импорт BOM или platform, возможно дальнейшее переопределение версии из других BOM или constraints 2 Импорт BOM/platform с более строгим ограничением на версии зависимостей 3 Использование зависимости не требует указания версии plugins { java } dependencies { implementation(platform("org.example:example-bom:1.1")) // implementation(enforcedPlatform("org.example:example-bom:1.1 implementation("org.example:example-lib") } 1 3 16.1
  22. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) 17.2
  23. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) platform в Gradle слабее аналогичного импорта BOM в Maven и позволяет повышение версии зависимости кроме указания strictly/reject в rich version 17.3
  24. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) platform в Gradle слабее аналогичного импорта BOM в Maven и позволяет повышение версии зависимости кроме указания strictly/reject в rich version enforcedPlatform в Gradle аналогичен или сильнее импорта BOM эффективно strictly для всех зависимостей 17.4
  25. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) platform в Gradle слабее аналогичного импорта BOM в Maven и позволяет повышение версии зависимости кроме указания strictly/reject в rich version enforcedPlatform в Gradle аналогичен или сильнее импорта BOM эффективно strictly для всех зависимостей использование constraints/platform/enforcedPlatform "протекает" в зависимые проекты 17.5
  26. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) platform в Gradle слабее аналогичного импорта BOM в Maven и позволяет повышение версии зависимости кроме указания strictly/reject в rich version enforcedPlatform в Gradle аналогичен или сильнее импорта BOM эффективно strictly для всех зависимостей использование constraints/platform/enforcedPlatform "протекает" в зависимые проекты возможность динамического создания виртуальных платформ 17.6
  27. ВАЖНЫЕ ОТЛИЧИЯ dependencyManagement в Maven наследуется по дереву предков platform/enforcedPlatform

    в Gradle указываются для конфигурации (аналог Maven scope) platform в Gradle слабее аналогичного импорта BOM в Maven и позволяет повышение версии зависимости кроме указания strictly/reject в rich version enforcedPlatform в Gradle аналогичен или сильнее импорта BOM эффективно strictly для всех зависимостей использование constraints/platform/enforcedPlatform "протекает" в зависимые проекты возможность динамического создания виртуальных платформ  Вышеописанные особенности Gradle platform проявляются при использовании Gradle Module Metadata 17.7
  28. ПРИМЕРЫ Jackson — исключительно компоненты библиотеки com.fasterxml.jackson:jackson-bom Spring Boot org.springframework.boot:spring-boot-dependencies Quarkus — модульная платформа

    io.quarkus.platform:quarkus-bom io.quarkus.platform:quarkus-camel-bom Micronaut io.micronaut.platform:micronaut-platform Java EE/Jakarta EE jakarta.platform:jakartaee-api и другие 18.5
  29. ПРОРЕЖИВАНИЕ ДЕРЕВА ЗАВИСИМОСТЕЙ exclude при декларации зависимости в Maven и

    Gradle exclude для конфигураций в Gradle banned dependencies при использовании maven-enforcer-plugin в Maven и с помощью constraints в Gradle capabilities в Gradle 19
  30. EXCLUDE Maven pom.xml <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> <scope>compile</scope> <exclusions> <exclusion>

    <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> Gradle build.gradle.kts // на уровне отдельной зависимости dependencies { implementation("org.apache.httpcomponents:httpclient:4.5.14 exclude(group = "commons-logging", module = "commons-logging") } } // на уровне конфигурации (аналог scope в Maven) configurations.implementation { exclude(group = "commons-logging", module = "commons-logging") } 20.1
  31. BANNED DEPENDENCIES Запрет использования определенных зависимостей по groupId, artifactId, version,

    classifier и т.п. pom.xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <executions> <execution> <id>banned-deps</id> <goals><goal>enforce</goal></goals> <configuration> <rules> <bannedDependecies> <excludes> <exclude>org.apache.logging.log4j:*:(,2.17.0]</exclude> </excludes> </bannedDependecies> </rules> 21
  32. CONSTRAINTS Является аналогом декларации в dependencyManagement Позволяет более жестко ограничить

    транзитивные зависимости Может выполнять роль аналогичную maven-enforcer-plugin build.gradle.kts dependencies { constraints { implementation("org.apache.logging.log4j:log4j-core") { version { strictly("[2.17.1,)") // or reject("[,2.17.1)") } } } } 22
  33. ИЗМЕНЕНИЕ GAV-КООРДИНАТ И ИМЁН ПАКЕТОВ Переименования при изменении major версии

    Изменение требований к окружению (версии JDK, стандартной библиотеки Kotlin/Scala) 23.2
  34. ИЗМЕНЕНИЕ GAV-КООРДИНАТ И ИМЁН ПАКЕТОВ Переименования при изменении major версии

    Изменение требований к окружению (версии JDK, стандартной библиотеки Kotlin/Scala) Java EE и Jakarta EE 23.3
  35. ИЗМЕНЕНИЕ GAV-КООРДИНАТ И ИМЁН ПАКЕТОВ Переименования при изменении major версии

    Изменение требований к окружению (версии JDK, стандартной библиотеки Kotlin/Scala) Java EE и Jakarta EE Maven repo relocations 23.4
  36. ПРИМЕР: ASM asm:asm org.ow2.asm:asm Конфликт, варианты решения: 1. Maven: maven-enforcer-plugin

    banned dependencies 2. Gradle capabilities (включая org.gradlex.java-ecosystem-capabilities плагин) 3. exclude (Maven и Gradle) 25
  37. ПРИМЕР: BOUNCY CASTLE JCE PROVIDER org.bouncycastle:bcprov-jdk15on:1.70 org.bouncycastle:bcprov-jdk15to18:1.71+ org.bouncycastle:bcprov-jdk18on:1.71+ Конфликт, варианты

    решения: 1. Maven: banned dependency + зависимость для целевой JDK 2. Gradle capabilities (включая org.gradlex.java-ecosystem-capabilities плагин) 3. exclude (Maven и Gradle)  легко получить при использовании выравнивания версий с помощью virtual platform в Gradle или при использовании общей версии с помощью properties (как в Gradle, так и в Maven) 26
  38. ПРИМЕР: JAVA EE/JAKARTA EE javax.mail:javax.mail-api:1.6.x (javax.mail) jakarta.mail:jakarta.mail-api:1.6.x (javax.mail) jakarta.mail:jakarta.mail-api:2.1.x (jakarta.mail)

    Возможен конфликт, различное поведение разных систем сборки: 1. При наличии зависимостей требующих и javax.mail, и jakarta.mail: добавление javax.mail-api, jakarta.mail-api:2.+ 2. При наличии зависимости от javax.mail и непреднамеренного обновления jakarta.mail- api 1.6.x → 2.x: добавление явной зависимости javax.mail-api и exclude/ban jakarta.mail-api или использование механизма capabilities (в Gradle) downgrade через dependencyManagement/constraint 27
  39. ДИНАМИЧЕСКИЕ ВЕРСИИ Возможно указание диапазона версий для зависимости вида [1.0,2.0)

    или 1.+ Выбор конкретной версии в момент сборки возможно использовать для BOM/platform, но в Maven только 4.0+ Легко получить невоспроизводимую сборку Рекомендуется использовать version locking в случае Maven — сторонними плагинами, в Gradle — "из коробки"  Крайне желательно чтобы при публикации BOM не содержал динамических версий.  Похожая ситуация со SNAPSHOT-зависимостями (changing dependencies в терминологии Gradle) 28
  40. ТЕСТОВЫЕ ЗАВИСИМОСТИ Тестовые фреймворки Вспомогательные библиотеки (Hamcrest, AssertJ, REST Assured,

    Awaitility) Assets/fixtures Альтернативные реализации СУБД, брокеров очередей и т.п. Конфигурация для тестового окружения 29
  41. ОПЦИОНАЛЬНЫЕ ЗАВИСИМОСТИ Наборы зависимостей для поддержки фич: альтернативные адаптеры СУБД

    пулы соединений http-клиенты библиотеки логирования, сбора метрик, трассировки и т.п. Зависимости для совместимости с конкретным окружением: удалённые или добавленные API (javax.annotation, StAX) Java Cryptography Extensions (JCE)  В случае публикации Gradle Module Metadata (при нативной публикации в Gradle) возможно использование features 30
  42. SHADE/SHADOW ПЛАГИНЫ Релокация зависимостей лучше добавлять в scope runtime (в

    Maven) или конфигурацию runtimeOnly (в Gradle) Создание uber/fat-jar использовать transformer'ы как минимум для имплементаций сервисов SPI 31.2
  43. RUN-TIME УПРАВЛЕНИЕ ЗАВИСИМОСТЯМИ Использование явного classpath -cp ., -cp dir/*

    и почему их не стоит использовать -cp @file Class-Path в MANIFEST.MF 32.1
  44. RUN-TIME УПРАВЛЕНИЕ ЗАВИСИМОСТЯМИ Использование явного classpath -cp ., -cp dir/*

    и почему их не стоит использовать -cp @file Class-Path в MANIFEST.MF Uber/Fat-JAR с дополнительным загрузчиком классов Spring Boot Quarkus One-JAR, JarInJar и т.п. 32.2
  45. RUN-TIME УПРАВЛЕНИЕ ЗАВИСИМОСТЯМИ Использование явного classpath -cp ., -cp dir/*

    и почему их не стоит использовать -cp @file Class-Path в MANIFEST.MF Uber/Fat-JAR с дополнительным загрузчиком классов Spring Boot Quarkus One-JAR, JarInJar и т.п. Различные варианты изоляции на базе загрузчиков классов OSGi JBoss Modules Plexus Classworlds (Maven) Gradle 32.3