Перші кроки з java 9 і проект jigsaw - частина друга

  • 11.12.15 7:47 •
  • ph_piter •
  • # 272 861 •
  • Хабрахабр •
  • Переклад •
  • 7 •
  • 8800

- такий же як Forbes, тільки краще.

Після деякого зволікання публікуємо другу частину статті про проект Jigsaw і Java 9, що вийшла в блозі Codecentric. Переклад першої частини знаходиться тут.

Це друга частина статті для тих, хто хоче ближче познайомитися з проектом Jigsaw. У першій частині ми коротко обговорили, що таке модуль, і як була модулярізована виконуючого середовища Java Runtime. Потім ми розглянули простий приклад компіляції, упаковки та запуску модульного додатка.

Тут ми постараємося відповісти на наступні питання:

Візьмемо за основу приклад з частини 1 і продовжимо працювати з ним. Код і раніше знаходиться тут.

Надання права на читання конкретним модулів

Таким чином, лише addresschecker може отримати доступ до API zipvalidator. Дана вказівка ​​виконується на рівні пакетів, тому ніщо вам не заважає обмежити доступ для одних пакетів, в той же час надавши повний доступ для інших. Така практика іменується «кваліфікований експорт». Якщо модуль de.codecentric.nastymodule спробує звернутися до будь-якого типу з de.codecentric.zipvalidator.api. то виникне помилка компіляції:


Зверніть увагу: програма не лається на module-info.java. так як zipvalidator в принципі міг би експортувати видимі пакети в nastymodule. Наприклад, кваліфікований експорт можна застосувати, коли ви хочете модулярізовать внутрішню структуру вашої програми, але не хочете ділитися експортованими пакетами внутрішніх модулів з клієнтами.

Конфлікти між версіями модулів

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

  • Модулі доступні під час компіляції в різних каталогах або модульних jar, але ім'я у них все одно однакове
  • Різні версії одного і того ж модуля є різнойменними

Давайте спробуємо скомпілювати додаток за першим сценарієм. Скопіювали zipvalidator:


Дублюються модулі знаходяться в різних каталогах, але ім'я модуля залишається незмінним. Як Jigsaw реагує на це під час компіляції?

Отже, тут нам легко не відбутися. Jigsaw видає помилку компіляції, коли в дорозі до модулів присутні два однойменних модуля.

Що щодо другого випадку? Структура каталогів залишається колишньою, але тепер обидва zipvalidator'а отримують різні імена (de.codecentric.zipvalidator.v), і addresschecker читає обидва цих імені.

Розробник з готовністю проігнорує таке попередження і запустить додаток. Але Jigsaw явно не подобається те, що він побачить під час виконання:

Мені це здається малозрозумілим, по-моєму, помилку часу компіляції можна було б оформити і акуратніше. Я поцікавився в розсилці. чому був обраний саме такий варіант, але на момент написання статті відповіді ще не отримав.

Автоматичні модулі і безіменний модуль

До сих пір ми працювали в повністю модулярізованной середовищі. Але що робити в таких дуже ймовірних випадках, коли доведеться мати справу з немодульность Jar-файлами? Тут вступають в гру автоматичні модулі і безіменний модуль.

Почнемо з автоматичних модулів. Автоматичний модуль - це jar-файл, поставлений в modulepath. Після того, як ви його туди запишете, можна буде відповісти на три наступні питання про це модулі:

В: Яке його ім'я?
Про: це ім'я jar-файлу. Якщо ви поставите в шлях до модулів файл guava.jar, то отримаєте автоматичний модуль guava.

Це також означає, що ви не зможете використовувати Jar прямо зі сховищ Maven, так як guava-18.0 не є допустимим ідентифікатором Java.

В: Що він експортує?
Про: Автоматичний модуль експортує всі свої пакети. Отже, всі публічні типи будуть доступні будь-якому модулю, що читає автоматичний модуль.

Розглянемо приклад. Ми починаємо використовувати com.google.common.base.Strings в zipvalidator-е. Щоб дозволити такий доступ, ми повинні визначити ребро зчитування для автоматичного модуля Guava:

Для компіляції потрібно вказати файл guava.jar в шляху до модулів (він знаходиться в каталозі. / Jars):


Всі прекрасно компілюється і запускається.

(Між іншим, було не так просто запустити цю приклад. Працюючи зі складанням Jigsaw 86, я зіткнувся з деякими проблемами: система лаялася з приводу залежностей від модуля jdk.management.resource. Я запитав про це в розсилці, обговорення знаходиться тут.

Потрібно сказати, що в моєму рішенні я не користувався «ранньої» складанням (early access build), а збирав JDK сам. При роботі з OSX Mavericks виникли ще деякі проблеми, про що написано в тред, довелося змінити makefile, але в підсумку я все налагодив. Можливо, вам при роботі з наступними релізами доведеться зіткнутися вже з іншими проблемами).

Тепер саме час познайомити вас з паличкою-виручалочкою, яка незамінна при переході на Jigsaw. Цей інструмент називається jdeps. Він переглядає ваш немодулярізованний код і повідомляє вам про залежності.

jdeps -s. /jars/guava.jar
Маємо такий висновок:
guava.jar -> java.base
guava.jar -> java.logging
guava.jar -> not found

Це означає, що автоматичний модуль guava вимагає java.base. java.logging і ... "не знайдено". Що таке? Якщо прибрати перемикач -s. то jdeps йде з рівня модулів і спускається на крок вниз, на рівень пакетів (список трохи скорочений, так як пакетів у guava досить багато):

Тут видно, що пакет com.google.common.xml залежить від com.google.common.escape. який розташований в самому модулі, java.lang. який добре відомий, і від анотації javax.annotation. яка не знайдена. Робимо висновок, що нам потрібен jar з типами JSR-305, оскільки там міститься javax.annotation (до речі, я без них обходжуся - в моїх прикладах мені не потрібно ні один тип з цих пакетів, і ні компілятор, ні виконуюча середовище за цьому не заперечують).

Отже, що ж таке безіменний модуль? Знову відповімо на три питання:

В: Якого його ім'я?
Про: Як ви вже здогадалися, імені у нього немає

В: Що він вимагає?
Про: Безіменний модуль читає всі інші доступні модулі.

Отже, якщо ви не можете прочитати безіменний модуль з будь-яких ваших модулів, то в чому ж суть? На це питання допомагає відповісти наш старий знайомий - шлях до класів. Будь-який тип, лічений з шляху до класів (а не з шляху до модулів), автоматично поміщається в безіменному модулі - або, іншими словами, будь-який тип в безіменному модулі завантажується через шлях до класів. Оскільки безіменний модуль читає всі інші модулі, ми можемо звернутися до всіх експортованих типам з будь-якого типу, завантаженого через шлях до класів. В Java 9 використання шляху до класів та шляхи до модулів буде підтримуватися як окремо, так і спільно, для забезпечення зворотної сумісності. Розглянемо кілька прикладів.

Припустимо, у нас є акуратний модуль zipvalidator, але addresschecker як і раніше не модулярізован, у нього немає module-info.java. Структура наших початкових кодів буде такою:

Тепер є один каталог classpath, в якому міститься успадкований код, зав'язаний на доступ до zipvalidator, а також каталог modulepath, що містить модуль zipvalidator. Ми можемо компілювати наші модулі як зазвичай. Щоб скомпілювати успадкований код, нам буде потрібно надати інформацію про модульному коді. Просто запишемо її в шлях до класів:


Все працює як зазвичай.

Під час виконання перед нами відкривається дві можливості. А саме:
  • Записати модуль в шлях до класів
  • Змішати шлях до класів і шлях до модулів

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

працює точно як java-додаток, що використовується вами сьогодні.

З іншого боку, змішане використання шляху до модулів та шляхи до класів працює так:

Використовуємо одночасно два перемикача: -classpath і -modulepath. Доданий перемикач -addmods - при змішуванні шляху до класів та шляхи до модулів, ми не можемо просто так отримати доступ до будь-якого модулю в каталогах modulepath, а повинні конкретно вказати, які з них повинні бути доступні.

Цей підхід також працює нормально, але тут є заковика! Пам'ятайте, відповідь на питання "чого вимагає безіменний модуль" - це "все інші модулі". Якщо ми будемо використовувати модуль zipvalidator через modulepath, то зможемо працювати лише з його експортованими пакетами. Все інше призведе до IllegalAccessError під час виконання. Тому в такому випадку вам доведеться дотримуватися правил системи модулів.

Створення образів виконуючого середовища за допомогою jlink

Досить прикладів з модулями; з'явився ще один новий інструмент, який заслуговує на нашу увагу. jlink - це утиліта Java 9 для створення власних дистрибутивів JVM. Найцікавіше, що, завдяки новій модульній архітектурі JDK, ви можете самі вибирати, які модулі хочете включити в цей дистрибутив! Розглянемо приклад. Якщо ми хочемо створити образ виконуючого середовища, що містить наш addresschecker, то даємо команду:


Вказуємо всього три речі:

  • Шлях до модулів (в тому числі, наші спеціальні модулі і шлях до каталогу jmods в вашому JDK - тут знаходяться стандартні модулі java)
  • Модулі, які ви хочете включити в ваш дистрибутив
  • Каталог виведення

Команда створює наступну структуру:

linkedjdk /
+-- bin
| + - java
| L-- keytool
+-- conf
| + - net.properties
| L-- security
| + - java.policy
| L-- java.security
L-- lib
+-- classlist
+-- jli


От і все. У OSX Mavericks все це займає приблизно 47 MB. Ми також можемо задіяти архівацію і видалити деякі налагоджувальні можливості, які все одно не знадобляться нам в продакшені. Найкомпактніший дистрибутив, який мені вдалося створити, вийшов за допомогою такої команди:

Розмір дистрибутива зменшується приблизно до 18 MB, по-моєму - просто чудово. У Linux, ймовірно, можна стиснутися і до 13.

Виводиться список модулів, що містяться в цьому дистрибутиві

Отже, всі програми, які залежать від максимальної кількості цих модулів, можуть працювати на даній JVM. Але мені не вдалося отримати наш основний клас для запуску цього сценарію. Для цього я пішов іншим шляхом.

Уважний читач міг помітити, що другий виклик робиться до jlink, і шлях до модулів там інший, ніж при першому виклику. У другому випадку ми вказуємо шлях до каталогу bin. Цей каталог містить модульні jar-файли, і jar для addresschecker також містить в своєму маніфесті інформацію про основний класі. Утиліта jlink використовує цю інформацію, щоб додати додаткову інформацію в bin-каталог нашої JVM:

Отже, тепер ми можемо викликати наш додаток безпосередньо. Краса!

76185 is a valid zip code

Ось і підійшло до кінця наше знайомство з Jigsaw. Ми розглянули ряд прикладів, що ілюструють, що можна і чого не можна зробити за допомогою Jigsaw і Java 9. Jigsaw привносить корінні зміни, які не можна так запросто компенсувати за допомогою лямбда-виразів або ресурсів try-with. Весь наш тулчейн від складальних інструментів на кшталт Maven або Gradle до IDE доведеться адаптувати до модульною системою. На конференції JavaOne Ханс Доктер з Gradle Inc. прочитав доповідь про те, як можна приступити до написання модульного коду навіть на Java 8 і нижче. Gradle виконує перевірку під час компіляції і видає відмову, якщо цілісність модуля виявляється порушена. Ця (експериментальна) можливість була включена в останній реліз Gradle 2.9. Нас безумовно чекають цікаві часи!

Схожі статті