← Назад к вопросам

Что такое проблема зависимостей в теневых JAR?

2.7 Senior🔥 111 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проблема зависимостей в теневых JAR (Uber JAR)

Теневой JAR (Shadow JAR или Uber JAR) — это специальный архив, который содержит не только код приложения, но и все его зависимости (библиотеки, которые приложение использует). Проблема зависимостей в теневых JAR — это набор сложных проблем, которые могут возникнуть при упаковке и распределении приложения таким образом.

Что такое Shadow JAR

// Обычная структура JAR
// myapp.jar
// ├── com/example/Main.class
// ├── META-INF/MANIFEST.MF
// └── lib/ (НЕ содержит зависимости)

// Shadow JAR (Uber JAR)
// myapp-all.jar
// ├── com/example/Main.class
// ├── org/apache/commons/... (Apache Commons)
// ├── com/google/... (Google Guava)
// ├── ... все зависимости встроены
// └── META-INF/MANIFEST.MF

Основные проблемы

1. Конфликт версий (Version Conflict)

Если две библиотеки зависят от разных версий одного пакета:

// pom.xml
<dependencies>
    <!-- Библиотека A зависит от commons-io 2.10.0 -->
    <dependency>
        <groupId>org.apache</groupId>
        <artifactId>libraryA</artifactId>
        <version>1.0</version>
    </dependency>
    
    <!-- Библиотека B зависит от commons-io 2.8.0 -->
    <dependency>
        <groupId>org.apache</groupId>
        <artifactId>libraryB</artifactId>
        <version>2.0</version>
    </dependency>
</dependencies>

// В Shadow JAR обе версии присутствуют одновременно
// commons-io-2.10.0.jar
// commons-io-2.8.0.jar

2. Загрязнение пространства имён (Namespace Pollution)

// Обе библиотеки имеют класс с одинаковым полным названием
// libraryA: org/example/utils/StringHelper.class
// libraryB: org/example/utils/StringHelper.class

// В Shadow JAR второй класс перезаписывает первый!
// Это приводит к ошибкам выполнения (NoClassDefFoundError, LinkageError)

3. Проблема с изолированными классами (Sealed Packages)

// Если пакет запечатан (sealed package)
// org.apache.commons (all classes must come from commons.jar)

// И Shadow JAR содержит этот пакет из других JAR
// Возникает: java.lang.SecurityException: 
// "sealed package org.apache.commons cannot be extended"

Практические примеры проблем

Пример 1: Конфликт классов логирования

// Две библиотеки используют разные версии SLF4J
// Library A: slf4j-api-1.7.30
// Library B: slf4j-api-2.0.0

// В runtime возникает:
// Exception in thread main java.lang.LinkageError: 
// loader (instance of java.net.URLClassLoader): 
// attempted duplicate class definition for name

Пример 2: Проблема с конфигурационными файлами

// Несколько библиотек используют META-INF/services/
// library-a: META-INF/services/org.example.Service
//   содержит: com.example.ServiceImplA
// library-b: META-INF/services/org.example.Service
//   содержит: com.example.ServiceImplB

// В Shadow JAR: второй файл перезаписывает первый!
// Это приводит к тому, что ServiceImplA никогда не загружается
// (важно для SPI - Service Provider Interface)

Решение 1: Relocation (Переименование пакетов)

<!-- pom.xml с maven-shade-plugin -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.4.1</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <relocations>
                            <relocation>
                                <pattern>org.apache.commons</pattern>
                                <shadedPattern>shade.org.apache.commons</shadedPattern>
                            </relocation>
                            <relocation>
                                <pattern>com.google.guava</pattern>
                                <shadedPattern>shade.com.google.guava</shadedPattern>
                            </relocation>
                        </relocations>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
// После relocation код будет использовать переименованные классы
// import shade.org.apache.commons.io.IOUtils;

Решение 2: Исключение конфликтующих зависимостей

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>my-app</artifactId>
        <version>1.0</version>
        <exclusions>
            <exclusion>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.10.0</version>
    </dependency>
</dependencies>

Best Practices

  1. Избегай Shadow JAR когда возможно — используй правильное управление зависимостями
  2. Используй Relocation если Shadow JAR необходим
  3. Документируй исключения в pom.xml с комментариями
  4. Тестируй классы в runtime чтобы убедиться что правильные версии загружаются
  5. Используй Maven BOM (Bill of Materials) для согласования версий

Проблемы зависимостей в Shadow JAR — это серьёзная проблема, которая требует тщательного планирования и инструментов для её решения. Лучший подход — избегать Shadow JAR и использовать правильную архитектуру приложения с управлением зависимостями через Maven/Gradle BOM и правильное исключение конфликтов.

Что такое проблема зависимостей в теневых JAR? | PrepBro