Керівництво по Java 9: компіляція і запуск проекту [*]


Команди 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! за допомогою цієї команди:

curl - s "https://get.sdkman.io" | bash

Подивіться, яка збірка є останньою:

sdk list java

Потім встановите Java 9 :

sdk install java 9ea163

Тепер, якщо у вас встановлені інші версії Java, ви можете перемикатися між ними за допомогою команди:

sdk use java 

Компіляція і запуск “по-старому”

Спершу напишемо який-небудь код, щоб перевірити наші інструменти. Якщо не використати модульний дескриптор, то все виглядає так само, як і раніше.

Візьмемо цей простий Java-клас:

 package app; public class Main { public static void main ( String[] args ) { System.out.println ( "Hello Java 9" ); } }

Тепер, оскільки ми не використали ніяких особливостей Java 9, ми можемо скомпілювати все як завжди:

javac - d out src/app/Main.java

Команда створить файл класу out/app/Main.class. Запустити його можна так само, як і в минулих версіях:

java - cp out app.Main

Програма виведе Hello Java 9.

Тепер створимо бібліотеку Greeting також без особливостей Java 9, щоб подивитися, як це працює.

Створимо файл greeting/ser/lib/Greeting.java з наступним кодом:

 package lib; public class Greeting { public String hello (){ return "Hi there"!; } }

Змінимо клас Main для використання нашої бібліотеки :

 package app; import lib.Greeting; public class Main { public static void main ( String[] args ) { System.out.println ( new Greeting ().hello() ); } }

Скомпілюємо цю бібліотеку:

javac - d greeting/out greeting/src/lib/Greeting.java

Щоб показати, як працюють оригінальні Java-бібліотеки, ми перетворимо цю бібліотеку на jar-файл без дескрипторів модулів Java 9 :

mkdir libsjar cf libs/lib.jar - C greeting/out .

Команда створить файл libs/lib.jar, клас, що містить lib.Greeting.

Проглянути інформацію про jar-файл можна за допомогою опції tf:

jar tf libs/lib.jar

Команда повинна вивести:

META - INF/ META - INF/MANIFEST.MF lib/lib/Greeting.class

Теперь для компіляція app.Main нам необхідно вказати компілятору, де знайти клас lib.Greeting.

Використовуємо для цього cp (classpath):

javac - d out - cp libs/lib.jar src/app/Main.java

И те ж саме для запуску програми :

java - cp out:libs/lib.jar app.Main

Ми можемо упакувати додаток в jar-файл:

jar cf libs/app.jar - C out .

І потім запустити його:

java - cp 'libs/*' app.Main

Ось так виглядає структура нашого проекту на даний момент:

. ├── 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/) :

 module com.app { }

Команда для компіляції модуля в Java 9 відрізняється від того, що ми бачили раніше. Використання старої команди з додаванням модуля до списку файлів призводить до помилки:

javac - d out - cp 'libs/lib.jar' src/module - info.java src/app/Main.java # не працює
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:

mv libs/lib.jar libs/greetings - 1.0.jar

Це більш стандартне ім’я файлу, і тепер ми можемо сказати Java включити автоматичний модуль з прийнятним ім’ям greetings. І можемо викликати його з com.app модулю:

 module com.app { requires greetings; }

Модулі не додані в classpath. Як і звичайні jar-файли, вони використовують новий прапор --module - path (- p). Тепер ми можемо скомпілювати наші модулі наступною командою:

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:

java - p 'libs/greetings-1.0.jar:out' - m com.app/app.Main

Ми отримаємо виведення Hi, there.

Для створення і використання app.jar в якості виконуваного jar-файлу виконаєте наступні команди:

jar --create - f libs/app.jar - e app.Main - C out . java - p libs - m com.app

Наступним кроком буде модуляризація бібліотек, які використовуються нашим додатком.

Модуляризація бібліотек

Для модуляризації бібліотеки не можна зробити нічого кращого, ніж використати jdeps – інструмент для статистичного аналізу, який є частиною Java SE.

Наприклад, команда, яка дозволяє побачити залежності нашої невеликої бібліотеки, виглядає так:

jdeps - s libs/greetings - 1.0.jar

А ось результат її виконання :

greetings - 1.0.jar -> java.base

Як і очікувалося, бібліотека залежить тільки від java.base модуля.

Ми знаємо, що com.app залежить від модулю greetings. Давайте спробуємо використати jdeps, щоб він підтвердив нам це. Для цього треба видалити файли module - info.calss й app.jar і потім запустити jdeps:

zip - d libs/app.jar module - info.class

Результат:

deleting: module - info.class

Команда:

jdeps - s - cp libs/greetings - 1.0.jar libs/app.jar

Результат:

app.jar -> libs/greetings - 1.0.jar app.jar -> java.base

Добре, але можна краще. Ми можемо попросити jdeps автоматично згенерувати модульний дескриптор для набору jar-файлів. Просто вкажіть йому, куди зберігати згенеровані файли (наприклад, в теку generated - mods), і де знаходяться jar-файли:

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 з наступним вмістом:

module app { requires greetings; exports app; }
 module greetings { exports lib; }

Тепер ми можемо додати згенерований дескриптор для нашої бібліотеки в її початковий код, переупакувати її, і у нас вийде повністю модульне застосування:

javac - d greeting/out $ (find greeting/src - name *.java) jar cf libs/greetings - 1.0.jar - C greeting/out .

Тепер у нас є повністю модуляризировані бібліотека і додаток. Після видалення згенерованих і бінарних файлів, структура нашого додатку виглядає таким чином:

├── 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-файлів для усіх залежностей бібліотек, які ви додасте в секцію залежностей, і викачає їх, якщо необхідно:

 apply plugin: 'java' repositories { mavenLocal () mavenCentral ()} dependencies { compile 'io.javaslang:javaslang:2.0.6' } task listDeps { println configurations.compile*.absolutePath.join (' ') }

Якщо у вас немає Gradle, ви можете використати SdkMAN! для його установки:

sdk install gradle

Для отримання списку залежностей використайте наступну команду:

gradle listDeps

Отриману інформацію передайте jdeps для аналізу і автоматичної генерації метаданих.

Це файл, який jdeps виводить для javaslang.match:

 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 модуля без оптимізації, стискування або налагоджувальної інформації :

jlink - p $JAVA_HOME/jmods:libs --add - modules com.app  --launcher start - app=com.app  --output dist

Менший розмір може бути досягнутий використанням деяких прапорів jlink, таких як --strip - debug і --compress:

jlink - p $JAVA_HOME/jmods:libs --add - modules com.app  --launcher start - app=com.app  --strip - debug --compress=2 
--output small - dist

Розмір пакетів можна подивитися за допомогою команди du - sh:

du - sh small - dist dist
21m small - dist 35m dist

Для запуску додатка використайте лаунчер, що надається в директорії bin:

dist/bin/start-app

Вы повинні побачити повідомлення Hi there.

На цьому все. Розбір нововведень в Java 9 пропонуємо прочитати в нашій статті.

Переклад статті “A practical guide to Java 9 – compile, jar, run”

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *