Может ли программа зависеть от библиотеки во время компиляции, но не во время выполнения?

110

Я понимаю разницу между временем выполнения и временем компиляции и как различать их, но я просто не вижу необходимости проводить различие между зависимостями времени компиляции и времени выполнения .

Я задыхаюсь от этого: как программа может не зависеть во время выполнения от чего-то, от чего она зависела во время компиляции? Если мое приложение Java использует log4j, то ему нужен файл log4j.jar для компиляции (мой код интегрируется с методами-членами и вызывает их изнутри log4j), а также во время выполнения (мой код не имеет абсолютно никакого контроля над тем, что происходит, когда код внутри log4j .jar запущен).

Я читаю об инструментах разрешения зависимостей, таких как Ivy и Maven, и эти инструменты четко различают эти два типа зависимостей. Я просто не понимаю, зачем это нужно.

Может ли кто-нибудь дать простое объяснение типа «королевского английского», желательно с реальным примером, который мог бы понять даже такой бедняк, как я?

IAmYourFaja
источник
2
Вы можете использовать отражение и классы, которые не были доступны во время компиляции. Подумайте "плагин".
Пер Александерссон,

Ответы:

64

Зависимость времени компиляции обычно требуется во время выполнения. В maven compileзависимость с определенным диапазоном будет добавлена ​​в путь к классам во время выполнения (например, в войнах они будут скопированы в WEB-INF / lib).

Однако это не обязательно; например, мы можем скомпилировать определенный API, сделав его зависимостью во время компиляции, но затем во время выполнения включить реализацию, которая также включает API.

Могут быть отдельные случаи, когда проекту требуется определенная зависимость для компиляции, но тогда соответствующий код на самом деле не нужен, но они будут редкими.

С другой стороны, очень распространено включение зависимостей времени выполнения, которые не нужны во время компиляции. Например, если вы пишете приложение Java EE 6, вы компилируете с использованием API Java EE 6, но во время выполнения можно использовать любой контейнер Java EE; именно этот контейнер обеспечивает реализацию.

Зависимостей времени компиляции можно избежать, используя отражение. Например, драйвер JDBC можно загрузить с помощью a, Class.forNameа фактический загруженный класс можно настроить с помощью файла конфигурации.

Artefacto
источник
17
О Java EE API - разве не для этого предназначена «предоставленная» область зависимости?
Кевин
15
Примером, когда зависимость требуется для компиляции, но не требуется во время выполнения, является lombok (www.projectlombok.org). Jar используется для преобразования кода Java во время компиляции, но не требуется во время выполнения. Если указать область действия "при условии", jar не будет включен в war / jar.
Кевин
2
@Kevin Да, хороший момент, providedобласть действия добавляет зависимость времени компиляции без добавления зависимости времени выполнения от ожидания того, что зависимость будет обеспечиваться во время выполнения другими средствами (например, разделяемой библиотекой в ​​контейнере). runtimeс другой стороны, добавляет зависимость времени выполнения, не делая ее зависимостью времени компиляции.
Artefacto
Можно ли с уверенностью сказать, что обычно существует корреляция 1: 1 между «конфигурацией модуля» (используя термины Ivy) и основным каталогом в корне вашего проекта? Например, все мои тесты JUnit, которые зависят от JUnit JAR, будут находиться в тестовом / корневом каталоге и т. Д. Я просто не понимаю, как одни и те же классы, упакованные в один и тот же исходный корень, могут быть «настроены» для зависимости от разных JAR в любой момент времени. Если вам нужен log4j, то вам нужен log4j; нет способа указать тому же коду вызывать вызовы log4j в одной конфигурации, но игнорировать вызовы log4j в некоторых конфигурациях "без ведения журнала", верно?
IAmYourFaja
30

Каждая зависимость Maven имеет область, определяющую, для какого пути к классам эта зависимость доступна.

Когда вы создаете JAR для проекта, зависимости не связываются с сгенерированным артефактом; они используются только для компиляции. (Однако вы все равно можете заставить maven включать зависимости во встроенную банку, см .: Включение зависимостей в банку с помощью Maven )

Когда вы используете Maven для создания файла WAR или EAR, вы можете настроить Maven для связывания зависимостей с сгенерированным артефактом, а также можете настроить его для исключения определенных зависимостей из файла WAR с использованием предоставленной области.

Наиболее распространенная область видимости - Compile Scope - указывает, что зависимость доступна для вашего проекта в пути к классам компиляции, в путях к классам компиляции и выполнения модульного теста и в конечном пути к классам времени выполнения при выполнении приложения. В веб-приложении Java EE это означает, что зависимость копируется в развернутое приложение. Однако в файле .jar зависимости не будут включены в область компиляции.

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

Наконец, Provided Scope указывает, что контейнер, в котором выполняется ваше приложение, предоставляет зависимость от вашего имени. В приложении Java EE это означает, что зависимость уже находится в пути к классам контейнера сервлета или сервера приложений и не копируется в развернутое приложение. Это также означает, что вам нужна эта зависимость для компиляции вашего проекта.

Корай Тугай
источник
@Koray Tugay Ответ более точен :) У меня быстрый вопрос, скажем, у меня есть зависимая банка с объемом времени выполнения. Будет ли maven искать банку во время компиляции?
gks
@gks Нет, во время компиляции этого не потребуется.
Корай Тугай
9

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

Многим библиотекам в свою очередь нужны другие библиотеки. Эти библиотеки не нужны во время компиляции, но необходимы во время выполнения. т.е. когда код действительно выполняется.

Питер Лоури
источник
не могли бы вы привести нам примеры таких библиотек, которые не понадобятся при компиляции, но понадобятся во время выполнения?
Cristiano
1
@Cristiano все библиотеки JDBC такие. Также библиотеки, реализующие стандартный API.
Питер Лоури
4

В общем, вы правы и, вероятно, это идеальная ситуация, если зависимости времени выполнения и времени компиляции идентичны.

Я приведу вам 2 примера, когда это правило неверно.

Если класс A зависит от класса B, который зависит от класса C, который зависит от класса D, где A - ваш класс, а B, C и D - классы из разных сторонних библиотек, вам нужны только B и C во время компиляции, и вам также нужно D на время выполнения. Часто программы используют динамическую загрузку классов. В этом случае вам не нужны классы, динамически загружаемые библиотекой, которую вы используете во время компиляции. Более того, часто библиотека выбирает, какую реализацию использовать во время выполнения. Например, SLF4J или Commons Logging могут изменить реализацию целевого журнала во время выполнения. Во время компиляции вам понадобится только сам SSL4J.

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

Надеюсь, мои объяснения помогут.

AlexR
источник
Можете ли вы уточнить, зачем нужен C во время компиляции в вашем примере? У меня сложилось впечатление (из stackoverflow.com/a/7257518/6095334 ), что необходимость C во время компиляции зависит от того, на какие методы и поля (из B) ссылается A.
Hervian
3

Обычно статический граф зависимостей является подграфом динамического, см., Например, эту запись в блоге автора NDepend .

Тем не менее, есть некоторые исключения, в основном зависимости, которые добавляют поддержку компилятора, которая становится невидимой во время выполнения. Например, для генерации кода через Lombok или дополнительных проверок через Checker Framework (подключаемый тип) .

Дэйв Фар
источник
2

Только что столкнулся с проблемой, которая отвечает на ваш вопрос. servlet-api.jarявляется временной зависимостью в моем веб-проекте и требуется как во время компиляции, так и во время выполнения. Но servlet-api.jarон также включен в мою библиотеку Tomcat.

Решение здесь состоит в том, чтобы сделать servlet-api.jarmaven доступным только во время компиляции и не упаковывать в мой военный файл, чтобы он не конфликтовал с servlet-api.jarсодержимым моей библиотеки Tomcat.

Я надеюсь, что это объясняет зависимость времени компиляции и времени выполнения.

Mayoor
источник
3
Ваш пример на самом деле неверно для данного вопроса, потому что это объясняет разницу между compileи providedоптическими прицелами и не между compileи runtime. Compile scopeнеобходимы во время компиляции и упакованы в ваше приложение. Provided scopeтребуется только во время компиляции, но не упакован в ваше приложение, потому что он предоставляется другими средствами, например, он уже находится на сервере Tomcat.
MJar
1
Что ж, я думаю, что это довольно хороший пример, потому что вопрос касался зависимостей времени компиляции и времени выполнения, а не областейcompile и runtime областей применения maven . Область providedвидимости - это способ, которым maven обрабатывает случай, когда зависимость времени компиляции не должна быть включена в пакет времени выполнения.
Кристиан Гаврон,
1

Я понимаю разницу между временем выполнения и временем компиляции и как их различать, но я просто не вижу необходимости проводить различие между зависимостями времени компиляции и времени выполнения.

Общие концепции времени компиляции и времени выполнения, а также зависимости Maven compileи runtimeобласти видимости - это две очень разные вещи. Вы не можете напрямую сравнивать их, поскольку у них разные рамки: общие концепции компиляции и времени выполнения широки, в то время как концепции maven compileи runtimescope касаются, в частности, доступности / видимости зависимостей в зависимости от времени: компиляция или выполнение.
Не забывайте, что Maven - это прежде всего javac/ javaоболочка, и что в Java у вас есть путь к классам времени компиляции, который вы указываете, javac -cp ... и путь к классам времени выполнения, который вы указываете java -cp ....
Было бы правильно рассматривать область Maven compileкак способ добавления зависимости как в компиляцию Java, так и в runtime classppath (javacи java), в то время как область Maven runtimeможно рассматривать как способ добавления зависимости только в среде выполнения Java classppath ( javac).

Я задыхаюсь от этого: как программа может не зависеть во время выполнения от чего-то, от чего она зависела во время компиляции?

То , что вы описали не имеет никакого отношения с runtimeи compileобъемом.
Похоже, что providedобласть видимости, которую вы указываете, зависит от зависимости во время компиляции, но не во время выполнения.
Вы используете его, поскольку вам нужна зависимость для компиляции, но вы не хотите включать ее в упакованный компонент (JAR, WAR или любой другой), потому что зависимость уже предоставлена средой: она может быть включена в сервер или любой другой путь к пути к классам, указанному при запуске приложения Java.

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

В этом случае да. Но предположим, что вам нужно написать переносимый код, который полагается на slf4j в качестве фасада перед log4j, чтобы иметь возможность позже переключиться на другую реализацию ведения журнала (log4J 2, logback или любую другую).
В этом случае в вашем pom вам нужно указать slf4j как compileзависимость (это значение по умолчанию), но вы укажете зависимость log4j как runtimeзависимость:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
    <scope>runtime</scope>
</dependency>

Таким образом, на классы log4j нельзя ссылаться в скомпилированном коде, но вы все равно сможете ссылаться на классы slf4j.
Если вы указали две зависимости со compileвременем, ничто не помешает вам ссылаться на классы log4j в скомпилированном коде, и вы можете создать нежелательную связь с реализацией ведения журнала:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
</dependency>

Обычно runtimeобласть видимости используется в объявлении зависимости JDBC. Чтобы писать переносимый код, вы не хотите, чтобы клиентский код мог ссылаться на классы конкретной зависимости СУБД (например, зависимость PostgreSQL JDBC), но вы все равно хотите включить его в свое приложение, поскольку во время выполнения классы необходимы для создания JDBC API работает с этой СУБД.

davidxxx
источник
0

Во время компиляции вы включаете контракты / api, которые ожидаются от ваших зависимостей. (например: здесь вы просто подписываете контракт с провайдером широкополосного доступа в Интернет) Фактически во время выполнения вы используете зависимости. (например: здесь вы фактически используете широкополосный доступ в Интернет)

Николае Даскалу
источник
0

Чтобы ответить на вопрос «как программа может не зависеть во время выполнения от чего-то, от чего она зависела во время компиляции?», Давайте рассмотрим пример процессора аннотаций.

Предположим, вы написали свой собственный обработчик аннотаций, и предположим, что он имеет зависимость во время компиляции, com.google.auto.service:auto-serviceтак что он может использовать @AutoService. Эта зависимость требуется только для компиляции процессора аннотаций, но не требуется во время выполнения: все другие проекты, зависящие от вашего процессора аннотаций для обработки аннотаций, не требуют зависимости от com.google.auto.service:auto-serviceво время выполнения (ни во время компиляции, ни в любое другое время) .

Это не очень часто, но бывает.

user23288
источник
0

Эта runtimeобласть предназначена для предотвращения добавления программистами прямых зависимостей к библиотекам реализации в коде вместо использования абстракций или фасадов.

Другими словами, он заставляет использовать интерфейсы.

Конкретные примеры:

1) Ваша команда использует SLF4J вместо Log4j. Вы хотите, чтобы ваши программисты использовали API SLF4J, а не Log4j. Log4j должен использоваться SLF4J только для внутренних целей. Решение:

  • Определите SLF4J как обычную зависимость времени компиляции
  • Определите log4j-core и log4j-api как зависимости времени выполнения.

2) Ваше приложение обращается к MySQL с помощью JDBC. Вы хотите, чтобы ваши программисты использовали код стандартной абстракции JDBC, а не напрямую против реализации драйвера MySQL.

  • Определите mysql-connector-java(драйвер MySQL JDBC) как зависимость времени выполнения.

Зависимости времени выполнения скрыты во время компиляции (выдают ошибки времени компиляции, если ваш код имеет «прямую» зависимость от них), но включаются во время выполнения и при создании развертываемых артефактов (файлы WAR, файлы JAR SHADED и т. Д.).

Агусти Санчес
источник