Команды java
и javac
редко используются Java-программистами. Такие инструменты, как Maven и Gradle делают их почти не нужными. Однако Maven и Gradle до сих пор не предоставляют полную поддержку для Java 9, поэтому, если вы хотите начать использовать её уже сейчас или просто хотите узнать некоторые полезные тонкости до официального релиза, стоит научиться вызывать java
, javac
и jar
для управления своим кодом.
Статья призвана показать примеры использования этих команд, а также то, как эти команды изменились по сравнению с прошлыми версиями Java. Дополнительно будут рассмотрены новые инструменты: jdeps
и jlink
. Предполагается, что вы хоть немного знакомы с предыдущими версиями команд java
/javac
/jar
и с модульной системой Java 9.
Установка Java 9
Сперва необходимо установить Java 9. Вы можете скачать её с сайта Oracle, но рекомендуется использовать SdkMAN!, так как в будущем он позволит вам с легкостью переключаться между разными версиями Java.
Можно установить SdkMAN! с помощью этой команды:
1 | curl -s "https://get.sdkman.io" | bash |
Посмотрите, какая сборка является последней:
1 | sdk list java |
Затем установите Java 9:
1 | sdk install java 9ea163 |
Теперь, если у вас установлены другие версии Java, вы можете переключаться между ними с помощью команды:
1 | sdk use java <version> |
Компиляция и запуск «по-старому»
Для начала напишем какой-нибудь код, чтобы проверить наши инструменты. Если не использовать модульный дескриптор, то все выглядит так же, как и раньше.
Возьмем этот простой Java-класс:
1 2 3 4 5 6 7 | package app; public class Main { public static void main( String[] args ) { System.out.println( "Hello Java 9" ); } } |
Теперь, так как мы не использовали никаких особенностей Java 9, мы можем скомпилировать всё как обычно:
1 | javac -d out src/app/Main.java |
Команда создаст файл класса out/app/Main.class
. Запустить его можно так же, как и в прошлых версиях:
1 | java -cp out app.Main |
Программа выведет Hello Java 9
.
Теперь создадим библиотеку Greeting также без особенностей Java 9, чтобы посмотреть, как это работает.
Создадим файл greeting/ser/lib/Greeting.java
со следующим кодом:
1 2 3 4 5 6 7 | package lib; public class Greeting { public String hello() { return "Hi there!"; } } |
Изменим класс Main
для использования нашей библиотеки:
1 2 3 4 5 6 7 8 9 | package app; import lib.Greeting; public class Main { public static void main( String[] args ) { System.out.println( new Greeting().hello() ); } } |
Скомпилируем эту библиотеку:
1 | javac -d greeting/out greeting/src/lib/Greeting.java |
Чтобы показать, как работают оригинальные Java-библиотеки, мы превратим эту библиотеку в jar-файл без дескрипторов модулей Java 9:
1 2 | mkdir libs jar cf libs/lib.jar -C greeting/out . |
Команда создаст файл libs/lib.jar
, содержащий класс lib.Greeting
.
Просмотреть информацию о jar-файле можно с помощью опции tf
:
1 | jar tf libs/lib.jar |
Команда должна вывести:
1 2 3 4 | META-INF/ META-INF/MANIFEST.MF lib/ lib/Greeting.class |
Теперь для компиляция app.Main
нам необходимо указать компилятору, где найти класс lib.Greeting
.
Используем для этого cp
(classpath):
1 | javac -d out -cp libs/lib.jar src/app/Main.java |
И то же самое для запуска программы:
1 | java -cp out:libs/lib.jar app.Main |
Мы можем упаковать приложение в jar-файл:
1 | jar cf libs/app.jar -C out . |
И затем запустить его:
1 | java -cp 'libs/*' app.Main |
Вот так выглядит структура нашего проекта на данный момент:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | . ├── greeting │ ├── out │ │ └── lib │ │ └── Greeting.class │ └── src │ └── lib │ └── Greeting.java ├── libs │ ├── app.jar │ └── lib.jar ├── out │ └── app │ └── Main.class └── src └── app └── Main.java |
Модуляризация проекта
Пока что ничего нового, но давайте начнем модуляризацию нашего проекта. Для этого создадим модульный дескриптор (всегда называется module-info.java
и размещается в корневой директории src/
):
1 2 | module com.app { } |
Команда для компиляции модуля в Java 9 отличается от того, что мы видели раньше. Использование старой команды с добавлением модуля к списку файлов приводит к ошибке:
1 | javac -d out -cp 'libs/lib.jar' src/module-info.java src/app/Main.java # не работает |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | src/app/Main.java:3: error: package lib does not exist import lib.Greeting; ^ src/app/Main.java:7: error: cannot find symbol System.out.println( new Greeting().hello() ); ^ symbol: class Greeting location: class Main 2 errors |
Чтобы понять, почему наш код не компилируется, необходимо понять, что такое безымянные модули.
Любой класс, который загружается не из именованного модуля, автоматически выполняет часть безымянного модуля. В примере выше перед созданием модульного дескриптора наш код не был частью какого-либо модуля, следовательно, он был ассоциирован с безымянным модулем. Безымянный модуль — это механизм совместимости. Проще говоря, это позволяет разработчику использовать в приложениях Java 9 код, который не был модуляризирован. По этой причине код, относящийся к безымянному модулю, имеет правила сродни Java 8 и ранее: он может видеть все пакеты, экспортируемые из других модулей, и все пакеты безымянного модуля.
Когда модульный дескриптор добавляется к модулю, его код больше не является частью безымянного модуля и не может видеть код других модулей, пока не импортирует их. В случае выше модуль com.app
не требует никаких модулей, поэтому модуль библиотеки Greeting для него не виден. Он может видеть только пакеты модуля java.base
.
Модули в Java 9, за исключением неуловимого безымянного модуля описанного выше, должны объявлять, какие другие модули им необходимы. В случае с модулем com.app
единственным требованием является библиотека Greeting. Но, как вы могли догадаться, эта библиотека (как и другие библиотеки, не поддерживающие Java 9) не является модулем Java 9. Как же нам включить её в проект?
В таком случае вам нужно знать имя jar-файла. Если у вас есть зависимость от библиотеки, которая не была конвертирована в модуль Java 9, вам надо знать, какой jar-файл вызывается для этой библиотеки, потому что Java 9 переведёт имя файла в валидный модуль.
Это называется автоматический модуль.
Так же, как и безымянные модули, автоматические модули могут читать из других модулей, и все их пакеты являются экспортируемыми. Но, в отличие от безымянных модулей, на автоматические можно ссылаться из явных модулей.
Чтобы узнать имя автоматического модуля, компилятор конвертирует неальфанумерические, поэтому что-то вроде slf4j-api-1.7.25.jar
превратится в имя модуля sl4j.api
.
У нас есть библиотека с именем lib.jar
. Давайте переименуем jar-файл в greetings-1.0.jar
:
1 | mv libs/lib.jar libs/greetings-1.0.jar |
Это более стандартное имя файла, и теперь мы можем сказать Java включить автоматический модуль с приемлемым именем greetings
. И можем вызывать его из com.app
модуля:
1 2 3 | module com.app { requires greetings; } |
Модули не добавлены в classpath
. Как и обычные jar-файлы, они используют новый флаг –module-path (-p)
. Теперь мы можем скомпилировать наши модули следующей командой:
1 | javac -d out -p 'libs/greetings-1.0.jar' src/module-info.java src/app/Main.java |
Чтобы запустить app.Main
командой java
мы можем использовать новый флаг –module (-m)
, который принимает либо имя модуля, либо шаблон module-name/main-class
:
1 | java -p 'libs/greetings-1.0.jar:out' -m com.app/app.Main |
И мы получим вывод Hi, there
.
Для создания и использования app.jar
в качестве исполняемого jar-файла выполните следующие команды:
1 2 | jar --create -f libs/app.jar -e app.Main -C out . java -p libs -m com.app |
Следующим шагом будет модуляризация библиотек, которые используются нашим приложением.
Модуляризация библиотек
Для модуляризации библиотеки нельзя сделать ничего лучше, чем использовать jdeps
— инструмент для статистического анализа, который является частью Java SE.
Например, команда, которая позволяет увидеть зависимости нашей небольшой библиотеки, выглядит так:
1 | jdeps -s libs/greetings-1.0.jar |
А вот результат её выполнения:
1 | greetings-1.0.jar -> java.base |
Как и ожидалось, библиотека зависит только от java.base
модуля.
Мы знаем, что com.app
зависит от модуля greetings
. Давайте попробуем использовать jdeps
, чтобы он подтвердил нам это. Для этого нужно удалить файлы module-info.calss
и app.jar
и затем запустить jdeps
:
1 | zip -d libs/app.jar module-info.class |
Результат:
1 | deleting: module-info.class |
Команда:
1 | jdeps -s -cp libs/greetings-1.0.jar libs/app.jar |
Результат:
1 2 | app.jar -> libs/greetings-1.0.jar app.jar -> java.base |
Хорошо, но можно лучше. Мы можем попросить jdeps
автоматически сгенерировать модульный дескриптор для набора jar-файлов. Просто укажите ему, куда сохранять сгенерированные файлы (например, в папку generated-mods
), и где находятся jar-файлы:
1 | jdeps --generate-module-info generated-mods libs/greetings-1.0.jar libs/app.jar |
Команда создаст два файла: generated-mods/app/module-info.java
и generated-mods/greetings/module-info.java
со следующим содержимым:
1 2 3 4 | module app { requires greetings; exports app; } |
1 2 3 | module greetings { exports lib; } |
Теперь мы можем добавить сгенерированный дескриптор для нашей библиотеки в её исходный код, переупаковать её, и у нас получится полностью модульное приложение:
1 2 | javac -d greeting/out $(find greeting/src -name *.java) jar cf libs/greetings-1.0.jar -C greeting/out . |
Теперь у нас есть полностью модуляризированные библиотека и приложение. После удаления сгенерированных и бинарных файлов, структура нашего приложения выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 | ├── greeting │ └── src │ ├── lib │ │ └── Greeting.java │ └── module-info.java ├── libs │ ├── app.jar │ └── greetings-1.0.jar └── src ├── app │ └── Main.java └── module-info.java |
Обратите внимание, что для получения хороших данных от jdeps
вы должны предоставить местоположение всех jar-файлов, которые используются в приложении, чтобы он мог составить полный граф модуля.
Наиболее простым способом получить список всех jar-файлов, которые используются в библиотеке, является использование скрипта Gradle. Он выведет пути локальных jar-файлов для всех зависимостей библиотек, которые вы добавите в секцию зависимостей, и скачает их, если необходимо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | apply plugin: 'java' repositories { mavenLocal() mavenCentral() } dependencies { compile 'io.javaslang:javaslang:2.0.6' } task listDeps { println configurations.compile*.absolutePath.join(' ') } |
Если у вас нет Gradle, вы можете использовать SdkMAN! для его установки:
1 | sdk install gradle |
Для получения списка зависимостей используйте следующую команду:
1 | gradle listDeps |
Полученную информацию передайте jdeps
для анализа и автоматической генерации метаданных.
Это файл, который jdeps
выводит для javaslang.match
:
1 2 3 4 5 6 7 8 9 | module javaslang.match { requires transitive java.compiler; exports javaslang.match; exports javaslang.match.annotation; exports javaslang.match.generator; exports javaslang.match.model; provides javax.annotation.processing.Processor with javaslang.match.PatternsProcessor; } |
Создание собственного образа среды выполнения
С помощью jlink Java-приложения могут распространяться как образы, которые не требуют установки JVM.
Следующая команда создает образ для нашего com.app
модуля без оптимизации, сжатия или отладочной информации:
1 2 3 | jlink -p $JAVA_HOME/jmods:libs --add-modules com.app \ --launcher start-app=com.app \ --output dist |
Меньший размер может быть достигнут использованием некоторых флагов jlink
, таких как –strip-debug
и –compress
:
1 2 3 4 | jlink -p $JAVA_HOME/jmods:libs --add-modules com.app \ --launcher start-app=com.app \ --strip-debug --compress=2 \ --output small-dist |
Размер пакетов можно посмотреть с помощью команды du -sh
:
1 | du -sh small-dist dist |
1 2 | 21M small-dist 35M dist |
Для запуска приложения используйте предоставляемый лаунчер в директории bin
:
1 | dist/bin/start-app |
Вы должны увидеть сообщение Hi there
.
На этом всё.
тоже так делаю, пользовался руководством https://javahelp.online/osnovy/kompilyatsiya-java-koda-iz-komandnoy-stroki
ВідповістиВидалити