Чи може програма залежати від бібліотеки під час компіляції, але не під час виконання?


110

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

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

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

Чи може хтось дати просте пояснення типу «англійська мова короля», бажано з фактичним прикладом, який навіть бідний сок, як я, міг зрозуміти?


2
Ви можете використовувати рефлексію та використовувати класи, які були недоступні під час компіляції. Подумайте «плагін».
Per Alexandersson

Відповіді:


64

Зазвичай залежність від компіляції часу вимагається під час виконання. У maven, compileмасштабна залежність буде додана до класу по часу виконання (наприклад, у війнах вони будуть скопійовані в WEB-INF / lib).

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

Можуть бути випадки, коли проект вимагає певної залежності для складання, але тоді відповідний код насправді не потрібен, але вони будуть рідкісними.

З іншого боку, у тому числі залежність від часу виконання, яка не потрібна під час компіляції, є дуже поширеною. Наприклад, якщо ви пишете заявку Java EE 6, ви компілюєте API API Java EE 6, але під час виконання будь-який контейнер Java EE може бути використаний; саме цей контейнер забезпечує реалізацію.

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


17
Про API Java EE - чи не для цього призначена "надана" область залежності?
Кевін

15
приклад, коли залежність потрібна для компіляції, але не потрібна під час виконання, - це lombok (www.projectlombok.org). Jar використовується для трансформації коду Java під час компіляції, але він взагалі не потрібен. Визначення сфери застосування "надано" призводить до того, що баночка не буде включена у війну.
Кевін

2
@Kevin Так, корисний момент, providedобласть додає залежність часу від компіляції, не додаючи залежність часу виконання від очікування, що залежність буде надана під час виконання іншими способами (наприклад, спільною бібліотекою в контейнері). runtimeз іншого боку, додає залежність від часу виконання, не роблячи це залежною від часу компіляції.
Артефакто

Тож чи можна впевнено сказати, що зазвичай існує кореляція 1: 1 між "модульною конфігурацією" (з використанням термінів Ivy) та головним каталогом під коренем проекту? Наприклад, усі мої тести JUnit, які залежать від JUnit JAR, будуть знаходитися під тестом / root і т. Д. Я просто не бачу, як ті самі класи, упаковані під один і той же вихідний корінь, можуть бути "налаштовані", щоб залежати від різних JAR в будь-який момент часу. Якщо вам потрібен log4j, тоді вам потрібен log4j; немає способу вказати той самий код, щоб викликати виклики log4j під 1 конфігурацією, але ігнорувати виклики log4j під деякою конфігурацією "не реєстрація", правда?
IAmYourFaja

30

Кожна залежність Maven має область, яка визначає, на якому класовому шляху ця залежність доступна.

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

Коли ви використовуєте Maven для створення файлу WAR або EAR, ви можете налаштувати Maven для поєднання залежностей із створеним артефактом, а також можете налаштувати його, щоб виключити певні залежності з файлу WAR за допомогою наданої області застосування.

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

Область виконання вказує на те, що для вашого проекту існує залежність від виконання тестування одиничних тестів та класів маршрутів виконання, але на відміну від області компіляції, вона недоступна під час складання вашої програми або її тестових одиниць. Залежність від виконання копіюється у вашу розгорнуту програму, але вона недоступна під час компіляції! Це добре для того, щоб ви не помилково залежали від конкретної бібліотеки.

Нарешті, наданий обсяг вказує, що контейнер, у якому виконується ваша програма, забезпечує залежність від вашого імені. У додатку Java EE це означає, що залежність вже є на класному шляху контейнера Servlet або сервера додатків і не копіюється у вашу розгорнуту програму. Це також означає, що ця залежність вам потрібна для складання вашого проекту.


@Koray Tugay Відповідь більш точна :) У мене є швидке запитання, мовляв, у мене є залежна банка з / за обсягом часу виконання. Чи шукає мавен під час складання баночку?
gks

@gks Ні, це не вимагатиме цього в процесі компіляції.
Корай Тугай

9

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

Багато бібліотек, по черзі потребують інших бібліотек. Ці бібліотеки не потрібні під час компіляції, але потрібні під час виконання. тобто коли код фактично виконується.


Ви можете навести нам приклади таких бібліотек, які не знадобляться при компіляції, але знадобляться під час виконання?
Кріштіано

1
@ Cristiano всі бібліотеки JDBC такі. Також бібліотеки, які реалізують стандартний API.
Пітер Лорі

4

Як правило, ви праві, імовірно, це ідеальна ситуація, якщо залежність від часу виконання та компіляції однакова.

Наведу 2 приклади, коли це правило є невірним.

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

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

Я сподіваюся, що мої пояснення допоможуть.


Чи можете ви детальніше пояснити, чому C потрібен під час компіляції у вашому прикладі? У мене складається враження (від stackoverflow.com/a/7257518/6095334 ), що потрібен C на час компіляції чи ні, залежить від того, які методи та поля (від B) A посилаються.
Гервіан

3

Зазвичай графік статичних залежностей є під графіком динамічного, див., Наприклад, цей запис у блозі від автора NDepend .

Однак, є деякі винятки, в основному залежності, які додають підтримку компілятора, яка стає невидимою під час виконання. Наприклад, для генерації коду через Lombok або додаткових перевірок, як через (pluggable type-) Checker Framework .


2

Щойно наткнувся на питання, яке відповідає на ваше запитання. servlet-api.jarє тимчасовою залежністю в моєму веб-проекті, і вона потрібна як під час компіляції, так і під час виконання. Але servlet-api.jarвін також включений до моєї бібліотеки Tomcat.

Рішення тут полягає в тому, щоб зробити servlet-api.jarMaven доступним лише під час компіляції, а не пакувати у моєму файлі війни, щоб він не зіткнувся з servlet-api.jarвміщеною в моїй бібліотеці Tomcat.

Сподіваюся, це пояснює залежність часу від компіляції та часу виконання.


3
Ваш приклад фактично невірний для даного питання, оскільки він пояснює різницю між compileі providedсферами, а не між compileі runtime. Compile scopeобоє потрібні під час компіляції та упаковуються у вашій програмі. Provided scopeпотрібен лише під час компіляції, але не упакований у вашій програмі, оскільки він надається іншими способами, наприклад, він уже є на сервері Tomcat.
MJar

1
Ну, я вважаю, що це досить хороший приклад, оскільки питання стосувалося залежностей від часу компіляції та часу виконання, а не про сфериcompile та runtime корисні показники . providedСфера шлях Maven обробляє випадок , коли під час компіляції залежність не повинна бути включена в пакет виконання.
Крістіан Гаврон

1

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

Загальні поняття часу компіляції та часу виконання, а також специфічні compileта runtimeмасштабні залежності Maven - це дві дуже різні речі. Ви не можете безпосередньо порівнювати їх, оскільки вони не мають однакового кадру: загальні концепції компіляції та часу виконання є широкими, тоді як поняття maven compileта area runtimeстосуються конкретно залежностей / видимості залежно від часу: компіляції чи виконання.
Не забувайте, що Maven - це перш за все a javac/ javawrapper і що у Java у вас є компільований часовий шлях компіляції, який ви вказуєте, javac -cp ... і клас маршруту виконання, який ви вказуєте java -cp ....
Неправильно було б розглядати compileобласть Maven як спосіб додати залежність як до компіляції Java, так і до класу виконання програми (javacта java) хоча runtimeобласть Maven може розглядатися як спосіб додати залежність лише у Java Ruppime classppath ( javac).

Мене задихається такий: як програма може не залежати від чогось під час виконання, від якого вона залежала під час компіляції?

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

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

У цьому випадку так. Але припустимо, що вам потрібно написати портативний код, який спирається на slf4j як фасад перед log4j, щоб пізніше перейти до іншої реалізації журналу (log4J 2, logback або будь-який інший).
У цьому випадку вам потрібно вказати 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), але ви хочете, щоб все-таки включити його у свою програму, оскільки під час виконання потрібні класи, щоб зробити API JDBC працює з цією СУБД.


0

Під час компіляції ви дозволяєте укладати контракти / api, яких ви очікуєте від своїх залежностей. (наприклад: тут ви просто підписуєте договір з широкосмуговим постачальником Інтернету) Під час роботи фактично ви використовуєте залежності. (наприклад: тут ви фактично використовуєте широкосмуговий Інтернет)


0

Щоб відповісти на питання "як програма може не залежати від чогось під час виконання, від якого вона залежала під час компіляції?", Розглянемо приклад процесора анотацій.

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

Це не дуже часто, але трапляється.


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 тощо).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.