Перейти к основному содержимому

Введение в Project Jigsaw

· 8 мин. чтения

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. Простое модульное приложение

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

./81e45d4c4f8bbaa6bc6720c7cd6822e2.png

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:

  1. компиляция-студенческая-модель
  2. компиляция-студент-сервис
  3. компиляция-студент-сервис-dbimpl
  4. компиляция-студент-клиент
  5. запустить-студент-клиент

Порядок выполнения для платформы Linux довольно прост:

  1. модули компиляции
  2. запустить-студент-клиент

В сценариях выше вы познакомитесь со следующими двумя аргументами командной строки:

  • –модуль-исходный-путь
  • –модуль-путь

Java 9 отказывается от концепции пути к классам и вместо этого вводит путь к модулю. Этот путь является местом, где можно обнаружить модули.

Мы можем установить это с помощью аргумента командной строки: --module-path .

Чтобы скомпилировать несколько модулей одновременно, мы используем --module-source-path . Этот аргумент используется для указания местоположения исходного кода модуля.

8. Модульная система, применяемая к исходному коду JDK

Каждая установка JDK поставляется с src.zip . Этот архив содержит базу кода для JDK Java API. Если вы извлечете архив, вы найдете несколько папок, некоторые из которых начинаются с java , несколько с javafx и остальные с jdk. Каждая папка представляет модуль.

./3838a2b3ed81ed0e7de7e47f1b20b617.png

Модули, начинающиеся с java , являются модулями JDK, те, которые начинаются с javafx , являются модулями JavaFX, а другие, начинающиеся с jdk , являются модулями инструментов JDK.

Все модули JDK и все пользовательские модули неявно зависят от модуля java.base . Модуль java.base содержит часто используемые API-интерфейсы JDK, такие как Utils, Collections, IO, Concurrency и другие. График зависимостей модулей JDK:

./9937b66afb7828d4d0bd8140e2d1247e.png

Вы также можете просмотреть определения модулей JDK, чтобы получить представление о синтаксисе их определения в файле module-info.java .

9. Заключение

В этой статье мы рассмотрели создание, компиляцию и запуск простого модульного приложения. Мы также видели, как исходный код JDK был модульным.

Есть еще несколько интересных функций, таких как создание меньшего времени выполнения с помощью инструмента компоновщика — jlink и создание модульных jar-файлов среди других функций. Мы подробно познакомим вас с этими функциями в следующих статьях.

Project Jigsaw — это огромное изменение, и нам придется подождать и посмотреть, как оно будет принято экосистемой разработчиков, в частности, создателями инструментов и библиотек.

Код, использованный в этой статье, можно найти на GitHub .