«1. Обзор

Java постоянно развивается и добавляет новые функции в JDK. И если мы хотим использовать эти функции в наших API, это может потребовать от нижестоящих зависимостей обновить свою версию JDK.

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

Однако в этом руководстве мы узнаем о файлах JAR с несколькими выпусками (MRJAR) и о том, как они могут одновременно содержать реализации, совместимые с разными версиями JDK.

2. Простой пример

Давайте рассмотрим служебный класс DateHelper, в котором есть метод проверки високосных лет. Предположим, что оно было написано с использованием JDK 7 и создано для работы на JRE 7+:

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 1 calendar API ");

        Calendar cal = Calendar.getInstance();
        cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(dateStr));
        int year = cal.get(Calendar.YEAR);

        return (new GregorianCalendar()).isLeapYear(year);
    }
}

Метод checkIfLeapYear будет вызываться из основного метода нашего тестового приложения:

public class App {
    public static void main(String[] args) throws Exception {
        String dateToCheck = args[0];
        boolean isLeapYear = DateHelper.checkIfLeapYear(dateToCheck);
        logger.info("Date given " + dateToCheck + " is leap year: " + isLeapYear);
    }
}

Давайте перенесемся в сегодняшний день. .

Мы знаем, что в Java 8 есть более лаконичный способ анализа даты. Итак, мы хотели бы воспользоваться этим и переписать нашу логику. Для этого нам нужно перейти на JDK 8+. Однако это означало бы, что наш модуль перестанет работать с JRE 7, для которой он изначально был написан.

И мы не хотим, чтобы это произошло, если только в этом нет крайней необходимости.

3. Файлы Jar с несколькими выпусками

Решение в Java 9 состоит в том, чтобы оставить исходный класс нетронутым и вместо этого создать новую версию, используя новый JDK, и упаковать их вместе. Во время выполнения JVM (версия 9 или выше) будет вызывать любую из этих двух версий, отдавая предпочтение самой высокой версии, которую поддерживает JVM.

Например, если MRJAR содержит Java версии 7 (по умолчанию), 9 и 10 одного класса, то JVM 10+ будет выполнять версию 10, а JVM 9 будет выполнять версию 9. В обоих случаях версия по умолчанию не выполняется, так как для этой JVM существует более подходящая версия.

Обратите внимание, что общедоступные определения новой версии класса должны точно соответствовать исходной версии. Другими словами, нам не разрешено добавлять какие-либо новые общедоступные API исключительно в новую версию.

4. Структура папок

Поскольку классы в Java напрямую сопоставляются с файлами по их именам, создание новой версии DateHelper в том же месте невозможно. Следовательно, нам нужно создать их в отдельной папке.

Давайте начнем с создания папки java9 на том же уровне, что и java. После этого давайте клонируем файл DateHelper.java, сохранив структуру папки пакета, и поместим его в java9:

src/
    main/
        java/
            com/
                baeldung/
                    multireleaseapp/
                        App.java
                        DateHelper.java
        java9/
            com/
                baeldung/
                    multireleaseapp/
                        DateHelper.java

Некоторые IDE, которые еще не поддерживают MRJAR, могут выдавать ошибки для дублирующихся классов DateHelper.java.

Мы рассмотрим, как интегрировать это с инструментами сборки, такими как Maven, в другом руководстве. А пока давайте просто сосредоточимся на основах.

5. Изменения в коде

Давайте перепишем логику клонированного класса java9:

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 9 Date Api");
        return LocalDate.parse(dateStr).isLeapYear();
    }
}

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

Это очень важно, потому что создание jar завершится ошибкой, если эти два правила не будут соблюдаться.

6. Кросс-компиляция в Java

Кросс-компиляция — это функция Java, позволяющая компилировать файлы для работы в более ранних версиях. Это означает, что нам не нужно устанавливать отдельные версии JDK.

Давайте скомпилируем наши классы, используя JDK 9 или выше.

Во-первых, скомпилируйте старый код для платформы Java 7:

javac --release 7 -d classes src\main\java\com\baeldung\multireleaseapp\*.java

Во-вторых, скомпилируйте новый код для платформы Java 9: ​​

javac --release 9 -d classes-9 src\main\java9\com\baeldung\multireleaseapp\*.java

Параметр выпуска используется для указания версии Java компилятор и целевой JRE.

7. Создание MRJAR

Наконец, создайте файл MRJAR, используя версию 9+:

jar --create --file target/mrjar.jar --main-class com.baeldung.multireleaseapp.App
  -C classes . --release 9 -C classes-9 .

Опция выпуска, за которой следует имя папки, позволяет упаковать содержимое этой папки в файл jar под значение номера версии:

com/
    baeldung/
        multireleaseapp/
            App.class
            DateHelper.class
META-INF/
    versions/
        9/
            com/
                baeldung/
                    multireleaseapp/
                        DateHelper.class
    MANIFEST.MF

В файле MANIFEST.MF установлено свойство, позволяющее JVM знать, что это файл MRJAR:

Multi-Release: true

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

Старые JVM игнорируют новое свойство, указывающее, что это файл MRJAR, и обрабатывают его как обычный файл JAR.

8. Тестирование

«Наконец, давайте протестируем нашу банку на Java 7 или 8:

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 1 calendar API 
Date given 2012-09-22 is leap year: true

А затем давайте снова протестируем банку на Java 9 или более поздней версии:

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 9 Date Api
Date given 2012-09-22 is leap year: true

9. Заключение

В этой статье мы Мы видели, как создать файл jar для нескольких выпусков на простом примере.

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