- Что такое самодостаточный jar?
- Java.lang.string
- Использование утилиты jar
- Новые заметные для разработчика фичи
- Плагин apache assembly
- Плагин apache shade
- Подключение jar-библиотек в ide netbeans
- Руководство по java 9 для тех, кому приходится работать с legacy-кодом
- Уголок java-разработчика: библиотеки на каждый день
Что такое самодостаточный jar?
JAR — это просто набор файлов классов. Чтобы быть исполняемым, его файл META-INF/MANIFEST.MF должен указывать на класс, реализующий метод main(). Это делается с помощью атрибута Main-Class. Вот пример:
Main-Class: path.to.MainClass
У MainClass метод static main(String… args)
Большинство программ зависит от существующего кода. Java предоставляет концепцию classpath. Classpath – это список элементов пути, который будет просматриваться в runtime, что поможет найти зависимый код. При запуске классов Java вы определяете classpath с помощью параметра командной строки -cp:
java -cp lib/one.jar;lib/two.jar;/var/lib/three.jar path.to.MainClass
Java runtime создает classpath, объединяя все классы из всех связанных JAR и добавляя при этом главный класс.
Новые проблемы возникают при дистрибуции JAR, которые зависят от других JAR:
Вам необходимо синхронизировать версии библиотек.
Что еще более важно, аргумент
-cp
не работает с JAR. Чтобы ссылаться на другие JAR, classpath должен быть задан в манифесте JAR через атрибутClass-Path
:
Class-Path: lib/one.jar;lib/two.jar;/var/lib/three.jar
3. По этой причине вам необходимо поместить JAR в то же место, относительное или абсолютное, в целевую файловую систему в соответствии с манифестом. Это означает, что сначала нужно открыть JAR и прочитать манифест.
Одним из способов решения этих проблем является создание уникальной единицы развертывания, которая содержит классы из всех JAR и может быть распространена как один артефакт. Существует несколько вариантов создания таких JAR:
Java.lang.string
Я думаю, что это один из основных моментов новых API в JDK 11. Здесь есть несколько полезных новых методов.
- boolean isBlank(): возвращает true, если строка пуста или содержит только пробелы, иначе false.
- Stream lines(): возвращает Stream из String, извлеченных из этой строки, поделенных разделителями строк.
- String repeat(int): возвращает строку, значение которой представляет собой конкатенацию этой строки, повторяющееся количество раз.
- String strip(): Возвращает строку, значение которой является этой строкой, при этом удаляются все пробелы в начале и в конце строки.
- String stripLeading(): возвращает строку, значение которой является этой строкой, при этом удаляются все пробелы в начале строки.
- String stripTrailing(): возвращает строку, значение которой является этой строкой, при этом удаляются все пробелы в конце строки.
Скорее всего, вы посмотрите на strip() и спросите: «Как это отличается от существующего метода trim()?» Ответ заключается в разнице определения пробелов. (прим. переводчика: если коротко, strip() лучше понимает юникод, подробный разбор на StackOverflow)
Использование утилиты jar
Также JAR-файл может быть создан с использованием утилиты jar, которая находится в каталоге JAVA_HOME/bin. Достаточно подробно эта утилита разобрана здесь: Creating a JAR File.В самом просто варианте вам надо создать классы в какой-либо директории, а потом вызвать эту утилиту с такой командой
Например, если у меня есть класс edu/javacourse/jar/SayHello.class, то команда создания архивного файла с именем say.jar выглядела бы так:
Опции «cf» означают: «c» — создать, «f» — файл. Ну а дальше я думаю очевидно — имя файла архива и список файлов. Причем в списке можно указывать много файлов через пробел, а также можно использовать маску — знаки «*» и «?». Если вы не знакомы с такими знаками — наберите в поисковике «маски файлов использование» и наслаждайтесь новыми знаниями.
В обоих случаях (IDE или утилита jar) в архиве будет создаваться файл MANIFEST.MF. Как я уже писал — есть смысл почитать о дополнительных свойствах JAR-файлов. Т.к. в документации все очень неплохо написано, не буду заниматься переводом. Так что перейдем к следующему пункту нашего путешествия по JAR-файлам.
Новые заметные для разработчика фичи
В JDK 11 довольно мало изменений, которые могли бы повлияет на стиль разработки. Присутствует небольшое изменение синтаксиса, множество новых API и возможность запуска приложений одним файлом без использования компилятора (прим. переводчика: так называемые shebang файлы).
JEP 323: Local-Variable Syntax for Lambda Parameters
В JDK 10 был введен вывод локальных переменных (или вывод типов) (JEP 286). Это упрощает код, поскольку вам больше не нужно явно указывать тип локальной переменной, вместо этого можно использовать var. JEP 323 расширяет использование этого синтаксиса, который теперь также применим к параметрам лямбда-выражений. Простой пример:
list.stream()
.map((var s) -> s.toLowerCase())
.collect(Collectors.toList());
Внимательный программист на Java указал бы, что лямбда-выражения уже имеют вывод типа, поэтому использование var было бы (в данном случае) излишним. Мы могли бы так же легко написать тот же код, что и:
list.stream()
.map(s -> s.toLowerCase())
.collect(Collectors.toList());
Зачем было нужно добавлять поддержку var? Ответом является один особый случай — когда вы хотите добавить аннотацию к лямбда-параметру. Это невозможно сделать без участия какого-либо типа. Чтобы избежать использования явного типа, мы можем использовать var для упрощения вещей, таким образом:
list.stream()
.map((@Notnull var s) -> s.toLowerCase())
.collect(Collectors.toList());
Это изменение потребовало изменений в спецификации языка Java (JLS), в частности:
Страница 24: The description of the var special identifier.Страница 627-630: Lambda parametersСтраница 636: Runtime evaluation of Lambda expressionsСтраница 746: Lambda syntax
JEP 330: Launch Single-File Source-Code Programs
Одним из критических замечаний к Java является избыточность синтаксиса, а «церемония», связанная с запуском даже тривиального приложения, может серьёзно повысить порог входа для новичка. Для написания приложения, которое просто печатает «Hello World!», требуется написать класс с общедоступным статическим void main методом и использовать метод System.out.println().
JEP 330 устраняет необходимость компиляции однофайлового приложения. Теперь достаточно ввести:
java HelloWorld.java
Java лаунчер идентифицирует, что файл содержит исходный код Java и скомпилирует код в *.class файл перед его выполнением.
Аргументы, помещенные после имени исходного файла, передаются в качестве аргументов при запуске приложения. Аргументы, помещенные перед именем исходного файла, передаются в качестве аргументов java лаунчеру после компиляции кода (это позволяет задавать такие вещи, как classpath, в командной строке). Аргументы, относящиеся к компилятору (например, путь к классам), также будут переданы в javac для компиляции.
Пример:
java -classpath /home/foo/java Hello.java Bonjour
Будет эквивалентно:
javac -classpath /home/foo/java Hello.java
java -classpath /home/foo/java Hello Bonjour
Этот JEP также обеспечивает поддержку «shebang» файлов. Чтобы уменьшить необходимость даже упоминать java лаунчер в командной строке, можно его включить в первую строку исходного файла. Например:
#!/usr/bin/java --source 11
public class HelloWorld {
...
Флаг -source с используемой версией Java является обязательным.
Плагин apache assembly
Assembly Plugin для Apache Maven позволяет разработчикам объединять результаты проекта в единый распространяемый архив, который также содержит зависимости, модули, документацию сайта и другие файлы.
— Плагин Apache MavenAssembly
Одним из принципов Maven является создание одного артефакта на проект. Хотя бывают исключения, например, Javadoc и исходный код, но в целом, если вам нужно несколько артефактов, нужно создавать один проект на каждый артефакт. Идея плагина Assembly заключается в том, чтобы обойти это правило.
Плагин Assembly полагается на специальный конфигурационный файл assembly.xml. Он позволяет вам выбирать, какие файлы будут включены в артефакт. Обратите внимание, что конечный артефакт не обязательно должен быть JAR: конфигурационный файл позволяет вам выбирать между доступными форматами, например, zip, war и т.д.
Плагин регулирует общие случаи использования, предоставляя предварительно определенные сборки (assemblies). Среди них – распространение самодостаточных JAR. Конфигурация выглядит следующим образом:
Ссылайтесь на предварительно определенную самодостаточную конфигурацию JAR
Установите главный класс для исполнения
Выполните
single
<goal>
Привяжите
<goal>
кpackage
после формирование исходного JAR
Запуск mvn package дает два артефакта:
Первый JAR имеет то же содержимое, что и тот, который был бы создан без плагина. Второй — это самодостаточный JAR. Вы можете выполнить его следующим образом:
java -jar target/executable-jar-0.0.1-SNAPSHOT.jar
В зависимости от проекта он может выполняться успешно… или нет. Например, в примере проекта Spring Boot он не работает со следующим сообщением:
%d [%thread] %-5level %logger - %msg%n java.lang.IllegalArgumentException:
No auto configuration classes found in META-INF/spring.factories.
If you are using a custom packaging, make sure that file is correct.
Причина в том, что разные JAR предоставляют разные ресурсы по одному и тому же пути, как например с META-INF/spring.factories.
Зачастую плагин следует стратегии “побеждает последний записавший”. Порядок основывается на имени JAR.
С помощью Assembly вы можете нек. Если вам нужно объединить ресурсы, вы, вероятно, захотите использовать плагин Apache Shade.
Плагин apache shade
Плагин Assembly является общим; плагин Shade ориентирован исключительно на задачу создания самодостаточных JAR.
Этот плагин предоставляет возможность упаковать артефакт в uber-jar, включая его зависимости, и оттенить — т.е. переименовать — пакеты некоторых зависимостей.
— Плагин Apache Maven Shade
Плагин основан на концепции преобразователей: каждый преобразователь отвечает за работу с одним типом ресурсов. Преобразователь может копировать ресурс как есть, добавлять статическое содержимое, объединять его с другими и т.д.
Хотя вы можете разработать свой преобразователь, плагин предоставляет набор готовых преобразователей:
Конфигурация плагина Shade к приведенному выше Assembly выглядит следующим образом:
shade
привязан к фазеpackage
по умолчаниюЭтот преобразователь предназначен для генерации файлов манифеста
Выполните ввод
Main-Class
Настройте финальный JAR так, чтобы он был многорелизным JAR. Это необходимо в случае, когда любой из исходных JAR является многорелизным JAR
Запуск mvn package дает два артефакта:
При работе с проектом, взятым за образец, финальный исполняемый файл все еще не работает так, как ожидалось. Действительно, во время сборки появляется множество предупреждений о дублировании ресурсов. Два из них мешают корректной работе проекта. Чтобы правильно их объединить, нам нужно посмотреть на их формат:
Чтобы настроить эти преобразователи, нам нужно добавить вышеуказанные библиотеки в качестве зависимостей к плагину Shade:
Объедините Log4J2
.dat
файлыОбъедините файлы
/META-INF/spring.factories
Добавьте необходимый код для преобразователей
Эта конфигурация работает! Тем не менее, есть оставшиеся предупреждения:
Вы можете добавить и настроить дополнительные преобразователи для устранения вышеупомянутых пунктов. В целом, весь процесс требует глубокого понимания каждого вида ресурсов и знаний от том, как с ними работать.
Подключение jar-библиотек в ide netbeans
Т.к. в данном курсе я использую для демонстрации NetBeans, то наверно будет неправильно не показать, как подлкючать JAR в этой среде. Как я уже упоминал, если появится время на другие IDE, я буду писать их в этой же статье. Но не думаю, что это будет в ближащее время.
Если вы посмотрите служебное окно «Projects», которое обычно находится слева, то вы в нем вы можете увидеть структуру вашего проекта, которая содержит раздел «Libraries». Я создал два проекта SayHello и UseHello
В открытом файле UseHello.java в самой первой строке видно, что компилятор выдает в ней ошибку — как мы уже знаем, она говорит об отсутствии нужного класса. В принципе IDE позволяют подключать не только готовые JAR-файлы, но и проекты, но мы не будем использовать эту функцию (в данном случае специально). Чтобы подключить библиотеку из проекта SayHello нам для начала надо собрать этот проект через команду «Build». Результат можем увидеть в каталоге dist. В нем мы можем увидеть файл SayHello.jar. Предвидя вопрос — а можно поменять имя файла — для того, чтобы файл создавался с другим именем, надо исправлять конфигурационный файл nbproject/project.properties. Найти в нем опцию с именем dist.jar И поменять имя файла. В общем не очень удобно. Так что я обычно этого не делаю.
Теперь нам надо подключить готовый JAR-файл к нашему второму проекту UseHello. Сделать это можно несколькими способами.
- Щелкнуть правой кнопкой мышки на пункте «Libraries» в структуре проекта и в нем выбрать пункт «Add JAR/Folder». После выбора файла его можно будет видеть под веткой «Libraries»
- Целкнуть правой кнопкой мышки на проекте UseHello в окне «Projects» и в выпадающем меню выбрать пункт (обычно самый нижний) «Properties». В открывшемся диалоговом окне выбрать слева пункт «Libraries» и в закладке «Compile» использовать кнопку «Add JAR/Folder».
Также предлагаю заглянуть на закладку «Run». В нем можно увидеть, что при запуске будет подключаться те библиотеки, которые подключаются при компиляции исходников («Classpath for Compiling Source»).
На этом можно закруглиться, хотя конечно же процесс познания бесконечен и вам наверняка встретится еще много интересной информации по использованию и созданию jar-файлов.
Руководство по java 9 для тех, кому приходится работать с legacy-кодом
Добрый вечер, коллеги. Ровно месяц назад мы получили контракт на перевод книги “
Modern Java
” от издательства Manning, которая должна стать одной из наших самых заметных новинок в будущем году. Проблема «Modern» и «Legacy» в Java настолько остра, что необходимость такой книги довольно назрела. Масштабы бедствия и способы решения возникающих проблем в Java 9 кратко описаны в статье Уэйна Ситрина (Wayne Citrin), перевод которой мы и хотим вам сегодня предложить.
Раз в несколько лет, с выходом новой версии Java, докладчики на JavaOne начинают смаковать новые языковые конструкции и API, хвалить их достоинства. А ретивым разработчикам тем временем не терпится внедрить новые возможности. Такая картина далека от реальности – она совершенно не учитывает, что большинство программистов заняты поддержкой и доработкой уже существующих приложений, а не пишут новые приложения с нуля.
Большинство приложений – в особенности коммерческие — должны быть обратно совместимы с более ранними версиями Java, в которых не поддерживаются все эти новые супер-пупер возможности. Наконец, большинство заказчиков и конечных пользователей, особенно в сегменте больших предприятий, настороженно относятся к радикальному обновлению Java-платформы, предпочитая выждать, пока она окрепнет.
Поэтому, как только разработчик собирается попробовать новую возможность, он сталкивается с проблемами. Вы бы стали использовать у себя в коде методы интерфейсов по умолчанию? Возможно – если вы счастливчик, и вашему приложению не требуется взаимодействовать с Java 7 или ниже. Хотите использовать класс java.util.concurrent.ThreadLocalRandom
для генерации псевдослучайных чисел в многопоточном приложении? Не выйдет, если ваше приложение должно работать одновременно на Java 6, 7, 8 или 9.
С выходом нового релиза разработчики, занятые поддержкой унаследованного кода, чувствуют себя как дети, вынужденные таращиться на витрину кондитерского магазина. Внутрь их не пускают, поэтому их удел — разочарование и фрустрация.
Итак, есть ли в новом релизе Java 9 что-нибудь для программистов, занятых поддержкой унаследованного кода? Что-то, способное облегчить им жизнь? К счастью — да.
Что приходилось делать при поддержке legacy-кода то появления Java 9
Конечно, можно впихнуть возможности новой платформы в унаследованные приложения, в которых нужно соблюдать обратную совместимость. В частности, всегда есть возможности воспользоваться преимуществами новых API. Однако, может получиться немного некрасиво.
Например, можно применить позднее связывание, если вы хотите получить доступ к новому API, когда вашему приложению также требуется работать со старыми версиями Java, не поддерживающими этот API. Допустим, вам требуется использовать класс java.util.stream.LongStream
, появившийся в Java 8, и вы хотите применить метод anyMatch(LongPredicate)
этого класса, но приложение должно быть совместимо с Java 7. Можно создать вспомогательный класс, вот так:
public classLongStreamHelper {
private static Class longStreamClass;
private static Class longPredicateClass;
private static Method anyMatchMethod;
static {
try {
longStreamClass = Class.forName("java.util.stream.LongStream");
longPredicateClass = Class.forName("java.util.function.LongPredicate");
anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass):
} catch (ClassNotFoundException e) {
longStreamClass = null;
longPredicateClass = null;
anyMatchMethod = null
} catch (NoSuchMethodException e) {
longStreamClass = null;
longPredicateClass = null;
anyMatchMethod = null;
}
public static boolean anyMatch(Object theLongStream, Object thePredicate)
throws NotImplementedException {
if (longStreamClass == null) throw new NotImplementedException();
try {
Boolean result
= (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate);
return result.booleanValue();
} catch (Throwable e) { // lots of potential exceptions to handle. Let’s simplify.
throw new NotImplementedException();
}
}
}
Есть способы упростить эту операцию, либо сделать ее более общей, либо более эффективной – идею вы уловили.
Вместо того, чтобы вызывать theLongStream.anyMatch(thePredicate)
, как вы поступили бы в Java 8, можно вызвать LongStreamHelper.anyMatch(theLongStream, thePredicate)
в любой версии Java. Если вы имеете дело с Java 8 – это сработает, но, если с Java 7 – то программа выбросит исключение NotImplementedException
.
Почему это некрасиво? Потому что код может чрезмерно усложниться, если требуется обращаться ко множеству API (на самом деле, даже сейчас, с единственным API, это уже неудобно). Кроме того, такая практика и не типобезопасна, поскольку в коде нельзя прямо упомянуть LongStream
или LongPredicate
. Наконец, такая практика гораздо менее эффективна, из-за издержек, связанных с рефлексией, а также из-за дополнительных блоков try-catch
. Следовательно, хотя и можно так сделать, это не слишком интересно, и чревато ошибками по невнимательности.
Да, вы можете обращаться к новым API, а ваш код при этом сохраняет обратную совместимость, но с новыми языковыми конструкциями вам это не удастся. Например, допустим, что нам нужно использовать лямбда-выражения в коде, который должен оставаться обратно-совместимым и работать в Java 7. Вам не повезло. Компилятор Java не позволит указать версию исходного кода выше целевой. Так, если задать уровень соответствия исходного кода 1.8 (т.е., Java 8), а целевой уровень соответствия будет 1.7 (Java 7), то компилятор вам этого не позволит.
Вам помогут разноверсионные JAR-файлы
Сравнительно недавно появилась еще одна отличная возможность использовать новейшие возможности Java, позволяя при этом приложениям работать со старыми версиями Java, где такие приложения не поддерживались. В Java 9 такая возможность предоставляется как для новых API, так и для новых языковых конструкций Java: речь о разноверисонных JAR-файлах.
Разноверсионные JAR-файлы почти не отличаются от старых добрых JAR-файлов, но с одной важнейшей оговоркой: в новых JAR-файлах появилась своеобразная «ниша», куда можно записывать классы, использующие новейшие возможности Java 9. Если вы работаете с Java 9, то JVM найдет эту «нишу», станет использовать классы из нее и игнорировать одноименные классы из основной части JAR-файла.
Однако, при работе с Java 8 или ниже, JVM неизвестно о существовании этой «ниши». Она игнорирует ее и использует классы из основной части JAR-файла. С выходом Java 10 появится новая аналогичная «ниша» для классов, использующих наиболее актуальные возможности Java 10 и так далее.
В JEP 238 – предложении на доработку Java, где описаны ращновенсионные JAR-файлы, приводится простой пример. Допустим, у нас есть JAR-файл с четырьмя классами, работающими в Java 8 или ниже:
JAR root
- A.class
- B.class
- C.class
- D.class
Теперь представим, что после выхода Java 9 мы переписываем классы A и B, чтобы они могли использовать новые возможности, специфичные для Java 9. Затем выходит Java 10, и мы вновь переписываем класс A, чтобы он мог использовать новые возможности Java 10. При этом, приложение по-прежнему должно нормально работать с Java 8. Новый разноверсионный JAR-файл выглядит так:
JAR root
- A.class
- B.class
- C.class
- D.class
- META-INF
Versions
- 9
- A.class
- B.class
- 10
- A.class
JAR-файл не только приобрел новую структуру; теперь в его манифесте указано, что этот файл разноверсионный.
Когда вы запускаете этот JAR-файл на Java 8 JVM, он игнорирует раздел META-INFVersions
, поскольку даже не подозревает о нем и не ищет его. Используются лишь оригинальные классы A, B, C и D.
При запуске под Java 9, используются классы, находящиеся в META-INFVersions9
, причем, они используются вместо оригинальных классов A и B, но классы в META-INFVersions10
игнорируются.
При запуске под Java 10 используются обе ветки META-INFVersions
; в частности, версия A от Java 10, версия B от Java 9 и используемые по умолчанию версии C и D.
Итак, если в вашем приложении вам нужен новый ProcessBuilder API из Java 9, но нужно обеспечить, чтобы приложение продолжало работать и под Java 8, просто запишите в раздел META-INFVersions9
JAR-файла новые версии ваших классов, использующие ProcessBuilder, а старые классы оставьте в основной части архива, используемой по умолчанию. Именно так проще всего использовать новые возможности Java 9, не жертвуя при этом обратной совместимостью.
В Java 9 JDK есть версия инструмента jar.exe, поддерживающая создание разноверсионных JAR-файлов. Такую поддержку также обеспечивают и другие инструменты, не входящие в JDK.
Java 9: модули, повсюду модули
Система модулей Java 9, (также известная под названием Project Jigsaw) – это, несомненно, крупнейшее изменение в Java 9. Одна из целей модуляризации — усилить действующий в Java механизм инкапсуляции, чтобы разработчик мог указывать, какие API предоставляются другим компонентам и мог рассчитывать, что JVM будет навязывать инкапсуляцию. При модуляризации инкапсуляция получается сильнее, чем при применении модификаторов доступа public/protected/private
у классов или членов классов.
Вторая цель модуляризации – указать, каким модулям необходимы для работы другие модули и еще до запуска приложения заблаговременно убедиться, что все необходимые модули на месте. В таком смысле модули сильнее традиционного механизма classpath, поскольку пути classpath заблаговременно не проверяются, и возможны ошибки из-за отсутствия необходимых классов. Таким образом, некорректный classpath может быть обнаружен уже тогда, когда приложение успеет проработать достаточно долго, либо после того, как оно будет много раз запущено.
Вся система модулей большая и сложная, и ее подробное обсуждение выходит за рамки этой статьи (Вот хорошее, подробное объяснение). Здесь я уделю внимание именно тем аспектам модуляризации, которые помогают разработчику при поддержке унаследованных приложений.
Модуляризация – хорошая штука, и разработчик должен стараться разбивать новый код на модули, когда это только возможно, даже если остаток приложения (пока) не модуляризован. К счастью, это легко сделать благодаря спецификации о работе с модулями.
Во-первых, JAR-файл становится модуляризован (и превращается в модуль) с появлением в нем файла module-info.class (скомпилированного из module-info.java) у корня файла JAR. module-info.java
содержит метаданные, в частности, название модуля, пакеты которого экспортируются (т.e., становятся видны извне), каких модулей требует данный модуль и некоторая иная информация.
Информация в module-info.class
видна лишь в случаях, когда JVM ищет ее – то есть, система трактует модуляризованные JAR-файлы точно как обычные, если работает со старыми версиями Java (предполагается, что код был скомпилирован для работы с более старой версией Java. Строго говоря, требуется немного похимичить, и все равно указывать в качестве целевой версии module-info.class именно Java 9, но это реально).
Таким образом, у вас должна остаться возможность запускать модуляризованные JAR-файлы с Java 8 и ниже при условии, что в иных отношениях они также совместимы с более ранними версиями Java. Также отметим, что файлы module-info.class
можно, с оговорками, помещать в версионируемых областях разноверсионных JAR-файлов.
В Java 9 существует как classpath, так и путь к модулю. and a module path. Classpath работает как обычно. Если поставить модуляризованный JAR-файл в classpath, он тратуется как любой другой JAR-файл. То есть, если вы модуляризовали JAR-файл, а ваше приложение еще не готово обращаться с ним как с модулем, его можно поставить в classpath, он будет работать как всегда. Ваш унаследованный код должен вполне успешно с ним справиться.
Также отметим, что коллекция всех JAR-файлов в classpath считается частью единственного безымянного модуля. Такой модуль считается самым обычным, однако, всю информацию он экспортирует другим модулям, и может обращаться к любым другим модулям. Таким образом, если у вас еще нет модуляризованного Java-приложения, но есть некие старые библиотеки, которые тоже пока не модуляризованы (и, вероятно, никогда не будут) – можно просто положить все эти библиотеки в classpath, и вся система будет нормально работать.
В Java 9 есть путь к модулям, работающий наряду с classpath. При использовании модулей из этого пути JVM может проверять (как во время компиляции, так и во время выполнения), все ли необходимые модули на месте, и сообщать об ошибке, если каких-то модулей не хватает. Все JAR-файлы в classpath, как члены безымянного модуля, доступны модулям, перечисленным в модульном пути – и наоборот.
Не составляет труда перенести JAR-файл из classpath в путь модулей – и пользоваться всеми преимуществами модуляризации. Во-первых, можно добавить файл module-info.class
в файл JAR, а затем поставить модуляризованный JAR-файл в путь модулей. Такой новоиспеченный модуль все равно сможет обращаться ко всем оставшимся JAR-файлам в classpath JAR, поскольку они входят в безымянный модуль и остаются в доступе.
Также возможно, что вы не хотите модуляризовать JAR-файл, либо, что JAR-файл принадлежит не вам, а кому-то еще, так что модуляризовать его сами вы не можете. В таком случае JAR-файл все равно можно поставить в путь модулей, он станет автоматическим модулем.
Автоматический модуль считается модулем, даже если в нем нет файла module-info.class
. Этот модуль одноименен тому JAR-файлу, в котором содержится, и другие модули могут явно затребовать его. Он автоматически экспортирует все свои публично доступные API и читает (то есть, требует) все прочие именованные модули, а также безымянные модули.
Таким образом, немодуляризованный JAR-файл из classpath можно превратить в модуль, вообще ничего для этого не делая. Унаследованные JAR-файлы автоматически превращаются в модули, просто в них не хватает некоторй информации, которая позволяла бы определить, все ли нужные модули на месте, либо определить, чего не хватает.
Не каждый немодуляризованный JAR-файл можно переместить в путь модулей и превратить его в автоматический модуль. Существует правило: пакет может входить в состав всего одного именованного модуля. То есть, если пакет находится более чем в одном JAR-файле, то всего один JAR-файл с этим пакетом в составе можно превратить в автоматический модуль. Остальные могут остаться в classpath и войти в состав безымянного модуля.
На первый взгляд, описанный здесь механизм кажется сложным, но на практике он весьма прост. На самом деле, речь в данном случае лишь о том, что можно оставить старые JAR-файлы в classpath или переместить их в путь модулей. Можно разбить их на модули или не делать этого. А когда ваши старые JAR-файлы будут модуляризованы, можно оставить их в classpath или переместить в путь модулей.
В большинстве случаев все должно попросту работать, как и раньше. Ваши унаследованные JAR-файлы должны прижиться в новой модульной системе. Чем больше вы модуляризуете код, тем больше информации о зависимостях требуется проверять, а недостающие модули и API будут обнаруживаться на гораздо более ранних этапах разработки и, возможно, избавят вас от больших кусков работы.
Java 9 «делает сама»: Модульный JDK и Jlink
Одна из проблем с унаследованными Java-приложениями заключается в том, что конечный пользователь может не работать с подходящей средой Java. Один из способов гарантировать работоспособность Java-приложения – предоставить среду выполнения вместе с приложением. Java позволяет создавать приватные (многократно распространяемые) JRE, которые можно распространять в пределах приложения. Здесь рассказано, как создать приватную JRE. Как правило, берется иерархия файлов JRE, устанавливаемая вместе с JDK, нужные файлы сохраняются, а также сохраняются опциональные файлы с тем функционалом, который может понадобиться в вашем приложении.
Процесс немного хлопотный: необходимо поддерживать иерархию установочных файлов, быть при этом внимательным, поэтому вы не пропускаете ни одного файла, ни одного каталога. Само по себе это не повредит, однако, все-таки хочется избавиться от всего лишнего, поскольку эти файлы занимают место. Да, легко поддаться и совершить такую ошибку.
Так что почему бы не перепоручить эту работу JDK?
В Java 9 можно создать самодостаточную среду, добавляемую к приложению – и в этой среде будет все необходимое для работы приложения. Больше не придется волноваться, что на пользовательском компьютере окажется не та среда для выполнения Java, не придется беспокоиться о том, что вы сами неправильно собрали приватную JRE.
Ключевой ресурс для создания таких самодостаточных исполняемых образов – это модульная система. Модуляризовать теперь можно не только собственный код, но и сам Java 9 JDK. Теперь библиотека классов Java – это коллекция модулей, из модулей же состоят и инструменты JDK. Система модулей требует указывать модули базовых классов, которые необходимы в вашем коде, и при этом вы указываете необходимые элементы JDK.
Чтобы собрать все это вместе, в Java 9 предусмотрен специальный новый инструмент под названием jlink. Запустив jlink, вы получаете иерархию файлов – именно тех, что нужны для запуска вашего приложения, ни больше, ни меньше. Такой набор будет гораздо меньше стандартной JRE, причем, он будет платформо-специфичным (то есть, подбираться для конкретной операционной системы и машины). Поэтому, если вы захотите создать такие исполняемые образы для других платформ, потребуется запустить jlink в контексте установки на каждой конкретной платформе, для которой вам нужен такой образ.
Также обратите внимание: если запустить jlink с приложением, в котором ничего не модуляризовано, у инструмента просто не будет нужной информации, позволяющей ужать JRE, поэтому jlink ничего не останется, кроме как упаковать целую JRE. Даже в таком случае вам будет немного удобнее: jlink сам упакует для вас JRE, поэтому можете не волноваться о том, как правильно скопировать файловую иерархию.
С jlink становится легко упаковать приложение и все, что необходимо для его запуска – и можете не волноваться, что сделаете что-нибудь неправильно. Инструмент упакует только ту часть среды исполнения, которая требуется для работы приложения. То есть, унаследованное Java-приложение гарантированно получит такую среду, в которой оно окажется работоспособным.
Встреча старого и нового
Одна из проблем, возникающих при поддержке унаследованного Java-приложения заключается в том, что вы оказываетесь лишены всех плюшек, появляющихся при выходе новой версии. В Java 9, как и в предшествующих версиях, появилась целая куча великолепных новых API и языковых возможностей, но разработчики (наученные горьким опытом) могут предположить, что попросту не смогут ими воспользоваться, не нарушив обратной совместимости с более ранними версиями Java.
Следует отдать должное проектировщикам Java 9: по-видимому, они это учли и хорошо поработали, чтобы предоставить эти новые возможности и тем разработчикам, которым приходится поддерживать старые версии Java.
Разноверсионные JAR-файлы позволяют разработчикам применять новые возможности Java 9 и выносить их в отдельную часть JAR-файла, где более ранние версии Java их не заметят. Таким образом, разработчику легко писать код для Java 9, оставить старый код для Java 8 и ниже и предоставить среде исполнения выбор – какие классы она сможет запустить.
Благодаря модулям Java, разработчику проще проверять зависимости, достаточно писать все новые JAR-файлы в модульном виде, а старый код оставлять немодуляризованным. Система очень щадящая, она ориентирована на постепенную миграцию и практически всегда поддерживает работу с унаследованным кодом, который «слыхом не слыхивал» о модульной системе.
Благодаря модульному JDK и jlink, можно с легкостью создавать исполняемые образы и гарантированно обеспечивать приложение такой средой исполнения, в которой будет все необходимое для работы. Ранее такой процесс был чреват множеством ошибок, но современный инструментарий Java позволяет его автоматизировать – и все просто работает.
В отличие от предыдущих релизов Java, в Java 9 вы можете свободно пользоваться всеми новыми возможностями, а если имеете дело со старым приложением и должны гарантировать его поддержку – то сможете удовлетворить потребности всех ваших пользователей, независимо от того, горят ли они желанием обновляться до самой актуальной версии Java.
Уголок java-разработчика: библиотеки на каждый день
За все время, проведенное в написании кода на Java, у меня сформировался определенный набор полезных
cторонних
библиотек, которые прочно засели в classpath, и без которых не обходится ни один день разработки, будь то написание чего-либо «на коленке» или работа над серьезным проектом. Речь идет не о «монстрах» вроде Spring, Struts, Hibernate (это другая история), а скорее об утилитах, которые заполняют пробелы в Java SE API и позволяют сэкономить десяток-другой лишних строк кода/минут тут и там. Этой информацией я бы и хотел поделиться с хабрасообществом — надеюсь, она пригодится особенно тем, кто только начинает штурмовать Java, и позволит немного, но увеличить производительность труда.
Итак, список наиболее часто используемых мной классов и методов с комментариями:
Apache Commons: Collections
Очень хорошая библиотека, во многом дополняющая Java SE Collections Framework. Очень полезна особенно тем, кто из-за «требований клиента» застрял на Java 2 SE 1.4.2 (бывает и такое) и до сих пор мечтает о LinkedHashMap 🙂
Apache Commons: DbUtils
Apache Commons: IO
Работа с файлами — не самая сильная сторона платформы, которую любят использовать критики Java (иногда, заслуженно). Например, запрос на реализацию
java.io.File.copy()висит
уже 10 лет. Commons IO частично выручает:
- FileUtils
- IOUtils
Apache Commons: Lang
Утилиты для работы со строками, reflection, сериализацией, объектами и системой. Пожалуй, наиболее часто используемая библиотека из Apache Commons.
- StringUtils — огромное количество методов для манипуляций со строками.
- StringEscapeUtils
- ToStringBuilder
- EqualsBuilder & HashCodeBuilder
- ExceptionUtils
Буду рад выслушать замечания и
ваши
советы по использованию jar-ов на каждый день.