1. Введение
Project Jigsaw — это зонтичный проект с новыми функциями, нацеленными на два аспекта:
- введение модульной системы в язык Java
- и его реализация в исходном коде JDK и среде выполнения Java.
В этой статье мы познакомим вас с проектом Jigsaw и его функциями и, наконец, завершим его простым модульным приложением.
2. Модульность
Проще говоря, модульность — это принцип проектирования, который помогает нам достичь:
- слабая связь между компонентами
- четкие контракты и зависимости между компонентами
- скрытая реализация с использованием строгой инкапсуляции
2.1. Единица модульности
Теперь возникает вопрос, что является единицей модульности? В мире Java, особенно с OSGi, файлы JAR считались единицей модульности.
JAR действительно помогли сгруппировать связанные компоненты вместе, но у них есть некоторые ограничения:
- явные контракты и зависимости между JAR
- слабая инкапсуляция элементов в JAR
2.2. банка ад
С JAR-файлами была еще одна проблема — JAR-ад. Несколько версий JAR-файлов, лежащих в пути к классам, привели к тому, что ClassLoader загрузил
первый найденный класс из JAR-файла с очень неожиданными результатами.
Другая проблема с JVM, использующей путь к классам, заключалась в том, что компиляция приложения будет успешной, но приложение завершится ошибкой во время выполнения с ClassNotFoundException
из-за отсутствующих JAR-файлов в пути к классам во время выполнения.
2.3. Новая единица модульности
Со всеми этими ограничениями при использовании JAR в качестве модуля модульности создатели языка Java придумали новую конструкцию языка, называемую модулями. Вместе с этим для Java запланирована совершенно новая модульная система.
3. Проект «Пила»
Основными мотивами этого проекта являются:
- создать модульную систему для языка — реализовано в рамках JEP 261
- применить его к исходному коду JDK — реализовано в рамках JEP 201
- модульность библиотек JDK — реализовано в рамках JEP 200
- обновить среду выполнения для поддержки модульности — реализовано в рамках JEP 220
- иметь возможность создавать меньшую среду выполнения с подмножеством модулей из JDK — реализовано в рамках JEP 282
Еще одной важной инициативой является инкапсуляция внутренних API в JDK, тех, которые находятся под солнцем*
, пакетов и других нестандартных API. Эти API никогда не предназначались для публичного использования и никогда не планировались для обслуживания. Но мощь этих API заставила разработчиков Java использовать их при разработке различных библиотек, фреймворков и инструментов. Были предоставлены замены для нескольких внутренних API, а остальные были перемещены во внутренние модули.
4. Новые инструменты модульности
- jdeps — помогает анализировать кодовую базу для выявления зависимостей от JDK API и сторонних JAR-файлов. Также упоминается название модуля, в котором можно найти JDK API. Это упрощает модульность кодовой базы.
- jdeprscan — помогает анализировать кодовую базу на наличие устаревших API.
- jlink — помогает создать меньшую среду выполнения за счет объединения модулей приложения и JDK.
- jmod — помогает в работе с файлами jmod. jmod — это новый формат упаковки модулей. Этот формат позволяет включать собственный код, файлы конфигурации и другие данные, которые не помещаются в файлы JAR.
5. Архитектура модульной системы
Модульная система, реализованная в языке, поддерживает их как конструкцию верхнего уровня, как и пакеты. Разработчики могут организовывать свой код в модули и объявлять зависимости между ними в соответствующих файлах определения модулей.
Файл определения модуля с именем module-info.java
содержит:
- его имя
- пакеты, которые он делает общедоступными
- модули, от которых зависит
- любые услуги, которые он потребляет
- любая реализация службы, которую он предоставляет
Последние два элемента в приведенном выше списке обычно не используются. Они используются только тогда, когда службы предоставляются и потребляются через интерфейс java.util.ServiceLoader .
Общая структура модуля выглядит так:
src
|----com.foreach.reader
| |----module-info.java
| |----com
| |----foreach
| |----reader
| |----Test.java
|----com.foreach.writer
|----module-info.java
|----com
|----foreach
|----writer
|----AnotherTest.java
На приведенном выше рисунке определены два модуля: com.foreach.reader
и com.foreach.writer
. Каждое из них имеет свое определение, указанное в module-info.java
и файлах кода, размещенных в com/foreach/reader
и com/foreach/writer
соответственно.
5.1. Терминология определения модуля
Давайте посмотрим на некоторые термины; мы будем использовать при определении модуля (т.е. в модуле- info.java)
:
модуль
: файл определения модуля начинается с этого ключевого слова, за которым следует его имя и определениетребует
: используется для указания модулей, от которых он зависит; имя модуля должно быть указано после этого ключевого словапереходный
: указывается послеключевого
слова require; это означает, что любой модуль, который зависит от определения модуля,требует транзитивного <modulename>
, получает неявную зависимость от <modulename>
exports
: используется для указания общедоступных пакетов внутри модуля; имя пакета должно быть указано после этого ключевого словаopens
: используется для указания пакетов, которые доступны только во время выполнения, а также доступны для самоанализа через API-интерфейсы Reflection; это очень важно для таких библиотек, как Spring и Hibernate, которые сильно зависят от API-интерфейсов Reflection;opens
также может использоваться на уровне модуля, и в этом случае весь модуль доступен во время выполненияиспользование
: используется для указания интерфейса службы, который использует этот модуль; имя типа, т. е. полное имя класса/интерфейса, должно быть указано после этого ключевого словаобеспечивает … с ..
.: они используются, чтобы указать, что он предоставляет реализации, указанные послеключевого слова
with
, для интерфейса службы, указанного после ключевого слова Provides ``
6. Простое модульное приложение
Давайте создадим простое модульное приложение с модулями и их зависимостями, как показано на диаграмме ниже:
com.foreach.student.model — это
корневой модуль. Он определяет класс модели com.foreach.student.model.Student
, который содержит следующие свойства:
public class Student {
private String registrationId;
//other relevant fields, getters and setters
}
Он предоставляет другие модули с типами, определенными в пакете com.foreach.student.model
. Это достигается определением его в файле module-info.java
:
module com.foreach.student.model {
exports com.foreach.student.model;
}
Модуль com.foreach.student.service
предоставляет интерфейс com.foreach.student.service.StudentService
с абстрактными операциями CRUD:
public interface StudentService {
public String create(Student student);
public Student read(String registrationId);
public Student update(Student student);
public String delete(String registrationId);
}
Это зависит от модуля com.foreach.student.model
и делает типы, определенные в пакете com.foreach.student.service
, доступными для других модулей:
module com.foreach.student.service {
requires transitive com.foreach.student.model;
exports com.foreach.student.service;
}
Мы предоставляем еще один модуль com.foreach.student.service.dbimpl
, который обеспечивает реализацию com.foreach.student.service.dbimpl.StudentDbService
для вышеуказанного модуля:
public class StudentDbService implements StudentService {
public String create(Student student) {
// Creating student in DB
return student.getRegistrationId();
}
public Student read(String registrationId) {
// Reading student from DB
return new Student();
}
public Student update(Student student) {
// Updating student in DB
return student;
}
public String delete(String registrationId) {
// Deleting student in DB
return registrationId;
}
}
Он напрямую зависит от com.foreach.student.service
и транзитивно от com.foreach.student.model,
и его определение будет таким:
module com.foreach.student.service.dbimpl {
requires transitive com.foreach.student.service;
requires java.logging;
exports com.foreach.student.service.dbimpl;
}
Последний модуль — это клиентский модуль, который использует модуль реализации службы com.foreach.student.service.dbimpl
для выполнения своих операций:
public class StudentClient {
public static void main(String[] args) {
StudentService service = new StudentDbService();
service.create(new Student());
service.read("17SS0001");
service.update(new Student());
service.delete("17SS0001");
}
}
И его определение:
module com.foreach.student.client {
requires com.foreach.student.service.dbimpl;
}
7. Компиляция и запуск примера
Мы предоставили сценарии для компиляции и запуска вышеуказанных модулей для платформ Windows и Unix. Их можно найти в рамках проекта core -java-9
здесь . Порядок выполнения для платформы Windows:
- компиляция-студенческая-модель
- компиляция-студент-сервис
- компиляция-студент-сервис-dbimpl
- компиляция-студент-клиент
- запустить-студент-клиент
Порядок выполнения для платформы Linux довольно прост:
- модули компиляции
- запустить-студент-клиент
В сценариях выше вы познакомитесь со следующими двумя аргументами командной строки:
–модуль-исходный-путь
–модуль-путь
Java 9 отказывается от концепции пути к классам и вместо этого вводит путь к модулю. Этот путь является местом, где можно обнаружить модули.
Мы можем установить это с помощью аргумента командной строки: --module-path
.
Чтобы скомпилировать несколько модулей одновременно, мы используем --module-source-path
. Этот аргумент используется для указания местоположения исходного кода модуля.
8. Модульная система, применяемая к исходному коду JDK
Каждая установка JDK поставляется с src.zip
. Этот архив содержит базу кода для JDK Java API. Если вы извлечете архив, вы найдете несколько папок, некоторые из которых начинаются с java
, несколько с javafx
и остальные с jdk.
Каждая папка представляет модуль.
Модули, начинающиеся с java
, являются модулями JDK, те, которые начинаются с javafx
, являются модулями JavaFX, а другие, начинающиеся с jdk
, являются модулями инструментов JDK.
Все модули JDK и все пользовательские модули неявно зависят от модуля java.base
. Модуль java.base
содержит часто используемые API-интерфейсы JDK, такие как Utils, Collections, IO, Concurrency и другие. График зависимостей модулей JDK:
Вы также можете просмотреть определения модулей JDK, чтобы получить представление о синтаксисе их определения в файле module-info.java
.
9. Заключение
В этой статье мы рассмотрели создание, компиляцию и запуск простого модульного приложения. Мы также видели, как исходный код JDK был модульным.
Есть еще несколько интересных функций, таких как создание меньшего времени выполнения с помощью инструмента компоновщика — jlink и создание модульных jar-файлов среди других функций. Мы подробно познакомим вас с этими функциями в следующих статьях.
Project Jigsaw — это огромное изменение, и нам придется подождать и посмотреть, как оно будет принято экосистемой разработчиков, в частности, создателями инструментов и библиотек.
Код, использованный в этой статье, можно найти на GitHub .