Что такое транзитивная зависимость в контексте систем сборки (Maven/Gradle)?

Ответ

Транзитивная зависимость — это зависимость, которая автоматически включается в проект, потому что она требуется одной из его прямых зависимостей. Если библиотека A зависит от библиотеки B, а B зависит от библиотеки C, то C становится транзитивной зависимостью для проекта A.

Как это выглядит на практике (Maven):

<!-- Прямая зависимость проекта -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>6.1.0</version>
</dependency>
<!-- spring-core, в свою очередь, зависит от commons-logging -->
<!-- Таким образом, commons-logging становится транзитивной зависимостью проекта -->

Потенциальные проблемы:

  1. Конфликт версий: Две прямые зависимости могут требовать разных, несовместимых версий одной транзитивной библиотеки.
  2. Нежелательные зависимости: В проект может «просочиться» ненужная или даже уязвимая библиотека.
  3. Раздувание размера: Включаются библиотеки, которые фактически не используются.

Управление транзитивными зависимостями:

  • Maven: Исключение (Exclusion)

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>6.1.0</version>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  • Gradle: Конфигурации implementation vs api и исключения.

    • implementation: Зависимость не «просачивается» к потребителям вашей библиотеки (рекомендуется по умолчанию).
    • api: Зависимость становится транзитивной для потребителей.
      dependencies {
      implementation('org.springframework:spring-core:6.1.0') {
          exclude group: 'commons-logging', module: 'commons-logging'
      }
      }

Почему это важно: Понимание транзитивности необходимо для управления размером приложения, разрешения конфликтов и обеспечения безопасности зависимостей.

Ответ 18+ 🔞

А, слушай, вот эта вся хуйня с транзитивными зависимостями — это ж классика, блядь! Сидишь такой, пишешь код, подключаешь одну библиотечку, а тебе в нагрузку, сука, пол-интернета подтягивается! Как будто ты заказал пиццу, а курьер привёз ещё и его бабушку с тремя котами, нахуй!

Транзитивная зависимость — это, грубо говоря, когда твой проект А говорит: «Мне нужна библиотека B». А библиотека B, хитрая жопа, отвечает: «Окей, но я без своей подружки C нихуя не работаю». И вот эта самая C, о которой ты даже не просил, уже лезет к тебе в проект, как назойливая мартышлюшка. Автоматом, нахуй!

Как это выглядит на практике (Maven):

Смотри, в pom.xml ты прописываешь:

<!-- Прямая зависимость проекта -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>6.1.0</version>
</dependency>
<!-- spring-core, в свою очередь, зависит от commons-logging -->
<!-- Таким образом, commons-logging становится транзитивной зависимостью проекта -->

И всё, привет! Ты хотел spring-core, а получил ещё и commons-logging в придачу. Ебушки-воробушки!

Потенциальные проблемы, от которых волосы дыбом:

  1. Конфликт версий, пиздец! Одна твоя библиотека тянет guava версии 20.0, а другая — guava версии 30.0. И они, пидары, между собой несовместимы! Начинается война, сборка падает, а ты сидишь и думаешь: «Ну нахуя?».
  2. Нежелательные зависимости. Может подтянуться какая-нибудь уязвимая хуйня десятилетней давности, о которой ты и не знал. А потом приходит секьюрити-сканер и говорит: «У тебя тут дыра размером с мою самооценку». Терпения ноль ебать.
  3. Раздувание размера. Сборка твоего приложения начинает весить как чугунный мост, потому что там лежит дохуя библиотек, которые ты в глаза не видел и в коде не используешь. Овердохуища мусора!

Как этим безобразием управлять?

  • В Maven: Исключение (Exclusion). Говоришь: «Слушай, дружок-библиотека, ты мне нравишься, но свою подружку-прилипалу оставь за дверью».

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>6.1.0</version>
        <exclusions>
            <exclusion>
                <!-- Вот эту самую нахлебницу и вышвыриваем! -->
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  • В Gradle: Тут тоже можно исключать, но есть ещё две важные штуки: implementation и api.

    • implementation: Это по умолчанию. Ты говоришь: «Библиотека, ты мне нужна, но своим друзьям о нас не болтай». Твои зависимости не станут транзитивными для тех, кто будет использовать твой код. Умно, блядь!
    • api: А это — «Ребята, вот мои друзья, знакомьтесь, они теперь и ваши друзья тоже». Зависимость просочится дальше.
      dependencies {
      implementation('org.springframework:spring-core:6.1.0') {
          // А если эта транзитивная зависимость всё-таки пролезла, выпинываем её
          exclude group: 'commons-logging', module: 'commons-logging'
      }
      }

Короче, зачем это всё нужно? А затем, чувак, чтобы твой проект не превратился в свалку случайного софта, чтобы он собирался, а не горел в аду конфликтов, и чтобы в нём не было дыр, через которые злоумышленник проедет на танке. Понимание этой хуйни — это не прихоть, а вопрос выживания в мире зависимостей, где все друг за друга цепляются, как пьяные за поручень в автобусе!