Ликбез: Область действия зависимостей
Этот пост в основном относится к тем, кто пишет или когда-нибудь хочет написать свою библиотеку (не обязательно для майна), которая потом будут использовать другие люди, а не только вы сами. Впрочем, если вы не имеете никакого отношения к библиотекам, этот пост тоже может быть для вас полезен.
Всё чаще вижу, что при разработке плагинов и библиотек используют системы сборки, такие как Gradle, Maven, Ant, и это просто замечательно! Но есть одна типичная ошибка, которая напрочь убивает всю магию автоматического разрешения зависимостей, которое предоставляют эти системы сборки - все забывают про область действия зависимостей (scope в терминах Maven, configuration в Gradle).
Приведу пример
Вы модный, стильный, молодёжный и поэтому используете систему сборки для управления зависимостями. И вот вы решили подключить в свой плагин бибилиотеку. Всё, вроде, просто - указываете её в секции зависимостей и, вуаля, всё работает. Но когда дело дошло до сборки плагина в jar, вы захотели включить библиотеку в свой jar, чтобы пользователям не пришлось беспокоиться о её скачивании. И вот тут возникла проблема - плагин стал весить 2 МБ вместо 100 Кб как раньше, а всё потому что разработчик библиотеки использовал в качестве зависимости Bukkit API (не заботясь об области действия зависимости) и весь Bukkit API тоже включился в ваш плагин. Ситуация не из приятных, но самое смешное, что её могло бы просто не быть если бы разработчк изменил всего одно слово в файле сценария сборки.
Maven: compile и provided
В Maven у зависимости (
dependency) есть свойство
scope, которое как раз отвечает за область видимости. Всего это свойство может принимать 6 вариантов значений, но нас интересют только два:
compile и
provided.
Если вы не знали о существовании опции scope или знали, но ничего в ней не указали, по умолчанию будет использовано значение "
compile". Область действия
compile означает, что эта зависимость будет доступна всегда, то есть и во время компиляции, и во время выполнения кода, и когда вы завтракаете хлопьями с молоком. Такие зависимости являются транзитивными, это значит, что они будут подключены и к зависимым проектам. Они, обычно, включаются в исполнимый jar.
Но на самом деле такое поведение зависимостей при разработке плагинов нам нужно редко и чаще зависимость не надо включать в конечный jar, так как мы рассчитываем, что она будет предоставлена на этапе выполнения кода. Для таких зависимостей есть область действия
provided. Такие зависимости не являются транзитивными и не включаются в jar.
Кратко
compile нужно использовать, когда вы подключаете api или библиотеку, которая должна являться неотъемлемой частью вашего проекта, и когда вам нужно, чтобы подключаемая зависимость была включёна в конечный jar-файл.
provide подходит для всех остальных случаев, то есть когда вы знаете, что зависимость будет предоставлена во время работы и вам не нужно включать её в jar-файл. Это именно то, что нужно когда вы подключаете в качестве зависимости Bukkit API, Spigot API, сторонний плагин и т.п.
По теме:
Maven - Introduction to the Dependency Mechanism (EN)
Gradle: api, implementation и compileOnly
(если вы думаете, что можете не читать то что написано в разделе про Maven, то вы ошибаетесь
)
В Gradle вместо термина "область действия" (
scope) используется термин "конфигурация" (
configuration).
Конфигураций бывает очень много, но нас интересуют только три из них:
api,
implementation и
compileOnly.
api это то же самое, что
compile в Maven, а
compileOnly - то же самое, что
provided (название
compileOnly выбрано из-за того, что такая зависимость нужна только на этапе компиляции и не должна включаться в jar).
Возникает совершенно справедливый вопрос "нафига нам знать еще про implementation?". Если объяснять хорошо и с примерами, то этот пост получится совсем уж длинным, поэтому попробую кратко. Зависимость c конфигурацией
implementation как и
api включается в jar, но не является транзитивной. Это сделано для того, чтобы зависимый проект не знал ничего о таких зависимостях, потому что они являются лишь частью реализации (которая может измениться), а не API. Думаю теперь стало понятно почему они называются api и
implementation.
Ксати, в примерах подключения зависимостей в Gradle, которые вы найдёте в интернете, скорее всего, не будет ни api, ни implementation, ни compileOnly, а везде будет использоваться compile. Я преднамеренно про него не рассказывал потому что эта конфигурация является deprecated и вместо неё нужно использовать implementation или api.
Кратко
implementation используется когда вам нужно, чтобы код подключаемой зависимости был включён в конечный jar-файл.
api используется, когда вы подключаете api, который должен являться неотъемлемой частью вашего проекта и может быть использован зависимыми проектами.
compileOnly подходит для всех остальных случаев, то есть когда вы знаете, что зависимость не нужно включать jar-файл. Например, Bukkit API, Spigot API, сторонний плагин и т.п.
По теме:
The Java Library Plugin - Gradle User Manual (EN)