Модульность
С выходом JDK 9 в языке Java появилась новая возможность — модульность. Модульность позволяет разбить код на отдельные структурные единицы — модули. Фактически модуль представляет группу пакетов или ресурсов, объединенных в одно целое и к которым можно обращаться по имени модуля.
До Java 9 было несколько уровней инкапсуляции функционала. Первый уровень представлял класс, в котором мы могли определить переменные и методы с различным уровнем доступа. Следующий уровень представлял пакет, который, в свою очередь, представлял коллекцию классов. Однако со временем этих уровней оказалось недостаточно. И модуль стал следующим уровнем инкапсуляции, который объединял несколько пакетов.
Модуль состоит из группы пакетов. Также модуль включает список все пакетов, которые входят в модуль, и список всех модулей, от которых зависит данный модуль. Дополнительно (но необязательно) он может включать файлы ресурсов или файлы нативных библиотек.
В качестве названия модуля может использоваться произвольный идентификатор из алфавитно-цифровых символов и знаков подчеркивания. Но рекомендуется, чтобы название модуля соответствовало названию, которого начинаются пакеты этого модуля.
Определим и используем простейший модуль. Допустим, файлы с исходными кодами помещаются в папку C:\java (либо какую-нибудь другую папку на жестком диске). Создадим в этой папке каталог, который назовем demo . Этот каталог будет представлять модуль.
В каталоге demo определим новый файл module-info.java со следующим кодом:
module demo
Этот файл представляет дескриптор модуля (module descriptor). Этот файл может содержать только определение модуля.
С помощью ключевого слова module определяется модуль, который называется demo, то есть так же, как и каталог, в котором данный файл расположен. После имени модуля с помощью фигурных скобок можно определить тело модуля, но в данном случае код модуля не содержит никаких инструкций.
Далее в каталоге demo создадим папку com . В папке com создадим папку metanit , а в папке com/metanit — папку hello .
В папке com/metanit/hello определим новый файл Hello.java :
package com.metanit.hello; public class Hello < public static void main(String[] args)< System.out.println("Hello Demo Module!"); >>
Название пакета файла — com.metanit.hello отражает структуру папок, в которых расположен файл. Сам файл определяет класс Hello, который в методе main выводит на консоль строку.
В итоге у нас получится следующая стуктура проекта:

Теперь скомпилируем все это. Для этого вначале перейдем в командной строке/терминале к папке, в которой находится модуль demo.
Затем для компиляции модуля выполним следующую команду:
javac demo/module-info.java demo/com/metanit/hello/Hello.java
После компиляции модуля demo выполним программу с помощью следующей команды:
java --module-path demo --module demo/com.metanit.hello.Hello
Параметр —module-path указывает на путь к модулю, а —module — на главный класс модуля.
При наборе команды вместо параметра —module-path можно указать его сокращение -p , а вместо параметра —module — сокращение -m .
И на консоли отобразится сообщение «Hello Demo Module!»
Java 9 Модули (проект Jigsaw)

В Java 9 появился долгожданный Project Jigsaw, или же просто модули. Они позволяют сделать приложение меньше в обьеме, более инкапсулированным и совершенным. В этой статье мы рассмотрим создание модулей.
2. Создание первого модуля
Для того чтобы создать модуль в IntelliJ IDEA необходимо в заранее созданном приложении нажать правой кнопкой на модуль и выбрать «new -> module» далее указываем имя и создаем наш первый модуль. В нашем случае имя модуля — human

Важный момент, для того чтобы модуль был полноценным, ему нужно добавить module-info.java, при чем создан он должен быть строго в папке src. Содержимое файла, в нашем случае, будет выглядеть так
Руководство по Java 9. Модульность.
Основным нововведением Java 9 было именно введение модульности. Это означает, что у нас появился новый компонент под названием модуль.
Модуль включает в себя код, данные и имя, по которому мы можем его идентифицировать.
Для того чтобы создать модуль, нам необходимо выполнить следующие шаги:
Создать проект в нужной директории. В моём случае это выглядит следующим образом:
Programming/Proselyte/Projects/Tutorials/Java9
src/net.proselyte.module
Внутри которой, необходимо создать файл module-info.java со следующим содержанием:
module net.proselyte.module <>
Создать класс Main:
public class Main < public static void main(String[] args) < System.out.println("Module TEST. "); >>

Зайти в директорию проекта и создать пакет с именем mods.
Внутри этого проекта создаём директорию с именем нашего модуля.
В результате мы должны получить проект со следующей структурой:
Шаг 5
Компилируем модуль в директорию mods:
javac -d mods/net.proselyte.module/ src/net.proselyte.module/module-info.java src/net.proselyte.module/net/proselyte/module/Main.java
Шаг 6:
Запускаем модуль:
java --module-path mods -m net.proselyte.module/net.proselyte.module.Main
В результате выполнения данной команды мы должны получить следующее:
Module test.
На этом мы заканчиваем разбор модульности в Java 9.
В следующей статье мы рассмотрим приватные методы в интерфейсах.
Модульность в Java 9
Основным нововведением Java 9 было именно введение модульности. Про эту фичу было много разговоров, дата релиза несколько раз переносилась, чтобы допилить все должным образом. В этом посте речь пойдет о том, что дает механизм модулей, и чего полезного Java 9 принесла в целом. Основой для поста послужил доклад моего коллеги — Сергея Малькевича.

Для реализации модулей в этой версии Java был выделен целый проект — Project Jigsaw — который включает в себя несколько JEP и JSR.

Для любителей официальной документации, ознакомиться более подробно с каждым JEP можно здесь.
Подробнее о Project Jigsaw
Project Jigsaw, который реализует модульность, начал разрабатываться в далеком 2005: сначала вышел JSR 277, а уже в 2008 началась непосредственная работа над проектом. Релиз состоялся только в 2017 году. То есть, для того, чтобы докрутить модули в Java, понадобилось почти 10 лет. Что, собственно, подчеркивает весь масштаб работы и изменений, которые были внесены в ходе реализации модульности.
Какие цели ставили перед собой разработчики:
- облегчить разработку больших приложений и библиотек;
- улучшить безопасность Java SE в целом, и JDK в частности;
- увеличить производительность приложений;
- создать возможность уменьшения размера JRE для запуска на небольших девайсах, чтобы не потреблять слишком много памяти;
- JAR HELL (об этом чуть позже).
Чего полезного принесла Java 9
До 9 версии, JDK и JRE были монолитными. Их размер рос с каждым релизом. Java 8 занимала уже сотни мегабайт, и все это разработчикам приходилось “таскать с собой” каждый раз, чтобы иметь возможность запускать Java приложения. Один только rt.jar весит порядка 60 Mb. Ну и сюда еще добавляем медленный старт и высокое потребление памяти. Тут на помощь пришла Java 9.
В JDK 9 было введено разделение на модули, а именно, JDK была разделена на 73 модуля. И с каждой новой версией количество этих модулей растет. В 11 версии это число близится к 100. Это разделение позволило разработчикам создать утилиту JLINK. С помощью JLINK можно создавать кастомные наборы JRE, которые будут включают только «нужные» модули, которые реально необходимы вашему приложению. Таким образом, простое приложение и какой-либо customJRE с минимальным (или небольшим) набором модулей в итоге может уместиться в 20 Mb, что не может не радовать.
Список модулей можно посмотреть здесь.
С приходом Java 9 поменялась структура JDK: теперь она идентична структуре JRE. Если раньше JDK включала папку JRE, где снова имеется bin и дублируются файлы, то теперь все выглядит следующим образом:

Модули
Собственно. что такое модуль? Модуль — это новый уровень агрегации пакетов и ресурсов (ориг. “a uniquely named, reusable group of related packages, as well as resources and a module descriptor”).
Модули поставляются в JAR файлах с пакетами и дескриптором модуля
module-info.java. Файл module-info.java содержит описание модуля:
имя, зависимости, экспортируемые пакеты, потребляемые и предоставляемые сервисы, разрешения для reflection доступа.
Примеры описания дескриптора модуля:
module java.sql
module jdk.javadoc
После ключевого слова module у нас идет имя пакета jdk.javadoc, который зависит от другого пакета java.xml и транзитивно зависит от других пакетов.
Давайте подробнее пройдемся по каждому из ключевых слов:
- requires указывает модули, от которых зависит текущий модуль;
- requires transitive — транзитивная зависимость — означает следующее: если модуль m1 транзитивно зависит от модуля m2, и мы имеем какой-то третий модуль mX, который зависит от m1 — модуль mX будет иметь доступ также и к m2;
- requires static позволяет указать compile-time зависимости;
- exports указывает пакеты, которые экспортирует текущий модуль (не включая “подпакеты”);
- exports. to… позволяет ограничить доступ: export com.my.package.name to com.specific.package; то есть можно открыть доступ к пакету нашего модуля только для какого-то другого(их) пакета(ов) другого модуля;
uses java.sql.Driver;
provides javax.tools.Tool with jdk.javadoc.internal.api.JavadocTool;
Допустим, у нас подключено несколько модулей, которые реализуют абстрактный сервис — MyService. При сборке приложения у нас есть возможность решить, какую реализацию сервиса использовать, “перетащив” нужные нам модули реализации сервиса на —module-path:
Iterable services = ServiceLoader.load(MyService.class);
Таким образом, возвращенный Iterator содержит список реализаций интерфейса MyService. Фактически, он будет содержать все реализации, найденные в модулях, найденных на —module-path.
Зачем в принципе были введены сервисы? Они нужны для того, чтобы показать, как наш код будет использован. То есть, здесь заключена семантическая роль. Также, модульность — это про инкапсуляцию и безопасность, так как мы можем сделать реализацию private и исключить возможность несанкционированного доступа через reflection.
Также, один из вариантов использования сервисов — это достаточно простая реализация плагинов. Мы можем реализовать интерфейс плагина для нашего приложения и подключать модули для работы с ними.
Вернемся к синтаксису описания модулей:
До 9ки через reflection мы имели доступ практически ко всему и могли делать все, что хотим и с чем хотим. А 9-ая версия, как уже упоминалось, позволяет обезопасить себя от “нелегального” reflection доступа.
Мы можем полностью открыть модуль для reflection доступа, объявив open:
open module my.module
Либо, мы можем указать какие либо пакеты для reflection доступа, объявив opens:
module my.module
Здесь же есть возможность использовать opens com.my.coolpackage to…, таким образом предоставляя reflection доступ пакету com.my.coolpackage из пакета, который укажем после to.
Типы модулей
Project Jigsaw классифицирует модули следующим образом:
- System Modules — Java SE и JDK модули. Полный список можно посмотреть, используя команду java —list-modules.
- Application Modules — модули нашего приложения, которые мы написали, а также те зависимости (от сторонних библиотек), которые использует наше приложение.
- Automatic Modules — это модули с открытым доступом, создаваемые Java автоматически из JAR-файлов. Допустим, мы хотим запустить наше приложение в модульном режиме, но оно использует какую-то библиотеку. В этом случае мы помещаем JAR-файл на —module-path и Java автоматически создает модуль с именем, унаследованным от имени JAR-файла.
- Unnamed Module — безымянный модуль, автоматически создаваемый из всех JAR-файлов, которые загружены на —class-path. Это универсальный модуль для обеспечения обратной совместимости с ранее написанным Java кодом.
Class-path vs module-path
С появлением модулей появилось новое понятие — module-path. По сути, это тот же class-path, но для модулей.
Запуск модульного приложения выглядит следующим образом:

В обычном режиме запуска мы указываем опции и полный путь к мейн классу. В случае, если мы хотим работать с модулями, мы также указываем опции и параметр -m либо -module, который как раз указывает на то, что мы будем запускать модули. То есть, мы автоматически переводим наше приложение в модульный режим. Далее мы указываем имя модуля и путь к мейн классу из модуля.
Также, если в обычном режиме мы привыкли работать с параметром -cp и —class-path, в режиме модульности мы прописываем новый параметр -p и —module-path, который указывает пути к используемым в приложении модулям.
Часто встречаюсь с тем, что разработчики не переходят на версии 9+, так как считают, что им придется работать с модулями. Хотя на самом деле, мы можем запускать наши приложения в старом режиме, попросту не прописывая параметр и не используя модули, а используя только другие новые фишки.
JAR HELL
Хочу также по диагонали остановится на проблеме Jar Hell.

Что такое Jar Hell в двух словах? Например, у нас есть какое-то наше приложение и оно зависит от библиотеки X и библиотеки Y. При этом, обе эти библиотеки зависят от библиотеки Z, но от разных версий: X зависит от версии 1, Y — от версии 2. Хорошо, если версия 2 обратно совместима с версией 1, тогда никаких проблем не возникнет. А если нет — очевидно, что мы получаем конфликт версий, то есть одна и та же библиотека не может быть загружена в память одним и тем же загрузчиком классов.
Как в этом случае выходят из ситуации? Есть стандартные методы, которые разработчики используют со времен самой первой Java, например, exclude, кто-то использует плагины для Maven, которые переименовывают названия корневых пакетов библиотеки. Либо же, разработчики ищут разные версии библиотеки X, чтобы подобрать совместимый вариант.
К чему это я: первые прототипы Jigsaw подразумевали наличие версии у модуля и позволяли загрузку нескольких версий через разные ClassLoader’ы, но позже от это отказались. В итоге, “серебряной пули”, которую многие ждали, не вышло.
Но, “прямо-из-коробки” нас немного обезопасили от подобных проблем. В Java 9 запрещены Split Packages — пакеты, которые разделены на несколько модулей. То есть, если у нас есть пакет com.my.coolpackage в одном модуле, мы не можем его использовать в другом модуле в рамках одного приложения. При запуске приложения с модулями, содержащими одинаковые пакеты, мы просто упадем. Это небольшое улучшение исключает возможность непредсказуемого поведения в связи с загрузкой Split пакетов.
Также, помимо самих модулей, есть еще механизм слоев или Jigsaw Layers, который также помогает справится с проблемой Jar Hell.
Jigsaw слой можно определить как некоторую локальную модульную систему. И здесь стоит отметить, что Split пакеты, о которых шла речь выше, запрещены только в рамках одного Jigsaw слоя. Модули с одинаковыми пакетами имеют место быть, но они должны принадлежать разным слоям.
Выглядит это следующим образом:

При старте приложения создается слой boot, куда входят модули платформы, загружаемые Bootstrap, добавочные модули платформы, загружаемые платформенным загрузчиком и модули нашего приложения, загружаемые Application загрузчиком.
В любой момент, мы можем создать свои слои и “положить” туда модули разных версий и при этом не упасть.
Есть отличный подробный доклад на YouTube на эту тему: Спасение от Jar Hell с помощью Jigsaw Layers
Заключение
Механизм модулей из Java 9 открывает нам новые возможности, при этом поддержка библиотек на сегодняшний день довольно небольшая. Да, люди запускают Spring, Spring Boot и так далее. Но большинство библиотек так и не перешло на полное использование модулей. Видимо поэтому, все эти изменения были восприняты довольно скептически техничесим сообществом. Модули предоставляют нам новые возможности, но вопрос востребованности остаётся открытым.
Ну и напоследок, предлагаю подборку материалов на эту тему: