Глава 48. Плагин Java Library.

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

48.1. Использование.

Чтобы использовать плагин Java Library, включите следующее в ваш сборочный скрипт:

Пример 48.1. Использование плагина Java Library

build.gradle

apply plugin: 'java-library'
	  

48.2. Разделение API и реализации.

Ключевое различие между стандаратным плагином Java и Java Library в том, что последний представляет концепцию API, доступного пользователям. Библиотека - это компонент Java, предназначенный для использования другими компонентами. Это очень частый случай в многопроектных сборках, а также, если у вас есть внешние зависимости.

Плагин предоставляет две конфигурации, которые могут использоваться для объявления зависимостей: api и implementation. Конфигурацию api стоит использовать для объявления зависимотей, которые экспортируются библиотечным API, тогда как конфигурацию implementation - для объявления зависимостей, которые внутренние для компонента.

Пример 48.2. Объявление зависимостей API и реализации

build.gradle

dependencies {
    api 'commons-httpclient:commons-httpclient:3.1'
    implementation 'org.apache.commons:commons-lang3:3.5'
}
	  

Зависимости из конфигураций api будут переходяще предоставлены пользователям библиотеки и таким образом будут находиться в пути к классам компиляции. Зависимости, найденные в конфигурации implementation, не будут, с другой стороны, предоставлены пользователям и, таким образов, не перетекут в путь к классам компиляции. У этого есть несколько преимуществ:

  • Зависимости больше не перетекают в путь к классам компиляции пользователей, так что вы никогда случайно не будете зависеть от переходной зависимости.
  • Более быстрая компиляции, благодаря уменьшенному размеру пути к классам.
  • Меньше перекомпиляций при изменении зависимостей реализации: потребителей больше не требуется перекомпилировать.
  • Более чистая публикация: при использовании в связке с новым плагином maven-publish, библиотеки Java создают файлы POM, которые в точности разделяют что требуется для компиляции с этой библиотекой, а что для ее использования во время выполнения (другими словами, не смешивается то, что требуется для компиляции самой библиотеки, а что для компиляции с ней).

Конфигурация compile все еще существует, но не стоит ее использовать, так как она не предлагает гарантий, которые предоставляют конфигурации api и implementation.

48.3. Распознавание зависимостей API и реализации.

Эта секция поможет вам заметить зависимости API и реализации в вашей коде, с помощью простых практичных правил. В своей основе, зависимость API - это тип, которые представляется в двоичном интерфейсе библиотеки, часто называемом ABI (двоичный интерфейс приложения). Это включает, но не ограничивается:

  • Типы, используемые в базовых классах или интерфейсах.
  • Типы, используемы в параметрах открытых методов, включая обобщенные параметры типов (где открытый - это видимый компиляторам. То есть члены public, protected и package private в мире Java).
  • Типы, используемые в открытых полях.
  • Открытые типы аннотаций.

В противоположность, любой тип, который используется в следующем списке, никак не связан с ABI и, таким образом, должен быть объявлен как зависимость implementation:

  • Типы, используемые исключительно в телах методов.
  • Типы, используемые исключительно в закрытых членах.
  • Типы, найденные исключительно во внутренних классах (будущие версии Gradle позволят вам объявить какие пакеты принадлежат отрытому API).

В следующем примере мы можем установить различие между завимостью API и реализации:

Пример 48.3. Устанавливаем различие между API и реализацией

src/main/java/org/gradle/HttpClientWrapper.java

// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class HttpClientWrapper {

    private final HttpClient client; // private member: implementation details

    // HttpClient is used as a parameter of a public method
    // so "leaks" into the public API of this component
    public HttpClientWrapper(HttpClient client) {
        this.client = client;
    }

    // public methods belongs to your API
    public byte[] doRawGet(String url) {
        GetMethod method = new GetMethod(url);
        try {
            int statusCode = doGet(method);
            return method.getResponseBody();

        } catch (Exception e) {
            ExceptionUtils.rethrow(e); // this dependency is internal only
        } finally {
            method.releaseConnection();
        }
        return null;
    }

    // GetMethod is used in a private method, so doesn't belong to the API
    private int doGet(GetMethod method) throws Exception {
        int statusCode = client.executeMethod(method);
        if (statusCode != HttpStatus.SC_OK) {
            System.err.println("Method failed: " + method.getStatusLine());
        }
        return statusCode;
    }
}
	  

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

С другой стороны, тип ExceptionUtils, пришедший из библиотеки commons-lang, используется только в теле метода, так что он зависимость реализации.

Таким образом, мы можем вывести, что commons-httpclient - это зависимость API, тогда как commons-lang зависимость реализации, которая транслируется непосредственно в сборочный файл:

Пример 48.4. Объявление зависимостей API и реализации

build.gradle

dependencies {
    api 'commons-httpclient:commons-httpclient:3.1'
    implementation 'org.apache.commons:commons-lang3:3.5'
}
	  

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

48.4. Конфигурации плагина Java Library.

Следующий граф описывает основные установки конфигураций при использовании плагина Java Library.

Основные установки конфигураций
  • Зеленые конфигурации должны использоваться пользователем для объявления зависимостей.
  • Розовые конфигурации используются при компиляции компонента или запуске с библиотекой.
  • Синие конфигурации внутренние конфигурации компонента, для его собственного использования.
  • Белые конфигурации - унаследованные от плагина Java.

Следующий граф описывает установки конфигураций теста:

Установки конфигураций теста

Конфигурации compile, testCompile, runtime и testRuntime, унаследованные от плагина Java, все еще доступны, но их использование не рекомендуется. Вам стоит избегать их, так как они оставлены только для обратной совместимости.

Роль каждой конфигурации описана в следующих таблицах:

Таблица 48.1. Плагин Java Library - конфигурации, используемые для объявления зависимостей
Имя конфигурацииРольМожет быть использованаМожет быть разрешенаОписание
apiОбъявление зависимостей APIнетнетЗдесь вы должны объявлять зависимости, которые переходяще экспортируются пользователям, для компиляции.
implementationОбъявление зависимостей реализациинетнетЗдесь вы должны объявлять зависимости, которые полностью внутренние и не предназначены для пользователей.
compileOnlyОбъявление только зависимостей компиляциидадаЗдесь вы должны объявлять зависимости, которые требуются только для компиляции, но не должны быть во время выполнения. Обычно сюда включаются зависимости, которые перекрываются, когда найдены во время выполнения.
runtimeOnlyОбъявление зависимостей времени выполнениянетнетЗдесь вы должны объявлять зависимости, которые требуются только во время выполнения, но не требуются во время компиляции.
testImplementationЗависимости тестовнетнетЗдесь вы должны объявлять зависимости, которые используются для компиляции тестов.
testCompileOnlyОбъявление зависимостей только для компиляции тестовдадаЗдесь вы должны объявлять зависимости, которые требуются только во время компиляции тестов, но не должны быть во время выполнения. Обычно сюда включаются зависимости, которые перекрываются, когда найдены во время выполнения.
testRuntimeOnlyОбъявление зависимостей времени выполнения тестовнетнетЗдесь вы должны объявлять зависимости, которые требуются во время выполнения тестов, но не во время компиляции.
Таблица 48.2. Плагин Java Library - конфигурации, используемые пользователями
Имя конфигурацииРольМожет быть использованаМожет быть разрешенаОписание
apiElementsДля компиляции с библиотекойданетЭто конфигурация предназначена для использования потребителями, для извлечения всех необходимых при компиляции с библиотекой элементов. В отличие от конфигурации default, из этой не перетекают зависимости реализации или времени выполнения.
runtimeElementsДля выполнения библиотекиданетЭто конфигурация предназначена для использования потребителями, для извлечения всех необходимых для запуска с этой библиотекой элементов.
Таблица 48.3. Плагин Java Library - конфигурации, используемые самой библиотекой
Имя конфигурацииРольМожет быть использованаМожет быть разрешенаОписание
compileClasspathДля компиляции этой библиотекинетдаЭто конфигурация содержит путь к классам компиляции и, таким образом, используется при вызове компилятора java для ее компиляции.
runtimeClasspathДля выполнения этой библиотекинетдаВ этой конфигурации содержится путь к классам времени выполнения.
testCompileClasspathДля компиляции тестов этой библиотекинетдаЭта конфигурация содержит путь к классам компиляции тестов этой библиотеки.
testRuntimeClasspathДля выполнения тестов этой библиотекинетдаЭта конфигурация содержит путь к классам времени выполнения тестов этой библиотеки.

48.5. Известные проблемы.

48.5.1. Совместимость с другими плагинами.

На данный момент, плагин Java Library ведет себя корректно только с плагином java. С другими плагинами, такими как Groovy, может вести себя некорректно. В частности, если плагин Groovy используется в дополнение к java-library, пользователи могут не получить классов Groove, когда пользуются библиотекой. Чтобы обойти это, вам надо явно привязать зависимость компиляции Groovy, вот так:

Пример 48.5. Настройка плагина Groovy для работы с Java Library

a/build.gradle

configurations {
    apiElements {
        outgoing.variants.getByName('classes').artifact(
            file: compileGroovy.destinationDir,
            type: JavaPlugin.CLASS_DIRECTORY,
            builtBy: compileGroovy)
    }
}
	  

48.5.2. Увеличенное потребление памяти для пользователей.

Когда проект использует плагин Java Library, пользователи будут непосредственно использовать выходную папку с классами этого проекта в своем пути к классам, вместо jar-файла, если бы использовался плагин Java. Побочное последствие этого состоит в том, что проверки актуальности будут требовать больше памяти, потому что Gradle будет делать снимки каждого файла класса вместо единого jar-файла. Это может вести в повышенному потреблению памяти для больших проектов.