1. Обзор
Java постоянно развивается и добавляет новые функции в JDK. И, если мы хотим использовать эти функции в наших API, это может обязать нижестоящие зависимости обновить свою версию JDK.
Иногда мы вынуждены ждать использования новых языковых функций , чтобы оставаться совместимыми.
Однако в этом уроке мы узнаем о многоразрядных JAR (MR JAR) и о том, как они могут одновременно содержать реализации, совместимые с различными версиями 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); } }
Проверьте, будет ли метод Високосного года вызван из метода main нашего тестового приложения:
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 в том же месте невозможно. Следовательно, нам нужно создать их в отдельной папке.
Давайте начнем с создания папки java 9 на том же уровне, что и java . После этого давайте клонируем DateHelper.java файл, сохраняющий структуру папок пакета, и поместите его в java 9:
src/ main/ java/ com/ baeldung/ multireleaseapp/ App.java DateHelper.java java9/ com/ baeldung/ multireleaseapp/ DateHelper.java
Некоторые идеи, которые еще не поддерживают MRJARs , могут выдавать ошибки для дублирования 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
Параметр release используется для указания версии компилятора Java и целевой JRE.
7. Создание MR JAR
Наконец, создайте файл MR JAR с помощью версии 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 знать, что это файл MR JAR:
Multi-Release: true
Следовательно, JVM загружает соответствующий класс во время выполнения.
Старые JVM игнорируют новое свойство, указывающее, что это файл MR JAR, и обрабатывают его как обычный файл JAR.
8. Тестирование
Наконец, давайте протестируем наш jar на 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
А затем давайте снова протестируем jar на 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 .