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

API Java 9 java.lang.Module

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

1. Введение

Следуя Руководству по модульности Java 9 , в этой статье мы собираемся изучить API java.lang.Module , который был представлен вместе с системой модулей платформы Java.

Этот API предоставляет способ программного доступа к модулю, получения конкретной информации из модуля и, как правило, работы с ним и его дескриптором модуля . ``

2. Чтение информации о модуле

Класс Module представляет как именованные, так и неименованные модули. Именованные модули имеют имя и конструируются виртуальной машиной Java при создании уровня модуля, используя граф модулей в качестве определения.

У безымянного модуля нет имени, и оно есть для каждого ClassLoader. Все типы, которых нет в именованном модуле, являются членами безымянного модуля, связанного с их загрузчиком классов.

Интересная часть класса Module заключается в том, что он предоставляет методы, которые позволяют нам получать информацию из модуля, такую как имя модуля, загрузчик классов модуля и пакеты внутри модуля.

Давайте посмотрим, как можно узнать, именованный модуль или безымянный.

2.1. Именованный или безымянный

Используя метод isNamed() , мы можем определить, именован модуль или нет.

Давайте посмотрим, как мы можем увидеть, является ли данный класс, например HashMap , частью именованного модуля, и как мы можем получить его имя:

Module javaBaseModule = HashMap.class.getModule();

assertThat(javaBaseModule.isNamed(), is(true));
assertThat(javaBaseModule.getName(), is("java.base"));

Давайте теперь определим класс Person :

public class Person {
private String name;

// constructor, getters and setters
}

Точно так же, как и для класса HashMap , мы можем проверить, является ли класс Person частью именованного модуля:

Module module = Person.class.getModule();

assertThat(module.isNamed(), is(false));
assertThat(module.getName(), is(nullValue()));

2.2. Пакеты

При работе с модулем может быть важно знать, какие пакеты доступны в модуле.

Давайте посмотрим, как мы можем проверить, содержится ли данный пакет, например, java.lang.annotation , в данном модуле:

assertTrue(javaBaseModule.getPackages().contains("java.lang.annotation"));
assertFalse(javaBaseModule.getPackages().contains("java.sql"));

2.3. Аннотации

Точно так же, как и для пакетов, можно получить аннотации, присутствующие в модуле, с помощью метода getAnnotations() .

Если в именованном модуле нет аннотаций, метод вернет пустой массив.

Посмотрим, сколько аннотаций присутствует в модуле java.base :

assertThat(javaBaseModule.getAnnotations().length, is(0));

При вызове безымянного модуля метод getAnnotations() вернет пустой массив.

2.4. ClassLoader

Благодаря методу getClassLoader() , доступному в классе Module , мы можем получить ClassLoader для данного модуля:

assertThat(
module.getClassLoader().getClass().getName(),
is("jdk.internal.loader.ClassLoaders$AppClassLoader")
);

2.5. Слой

Еще одна ценная информация, которую можно извлечь из модуля, — это ModuleLayer , представляющий слой модулей в виртуальной машине Java.

Модульный уровень информирует JVM о классах, которые могут быть загружены из модулей. Таким образом, JVM точно знает, членом какого модуля является каждый класс.

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

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

ModuleLayer javaBaseModuleLayer = javaBaseModule.getLayer();

Как только мы получили ModuleLayer , мы можем получить доступ к его информации:

assertTrue(javaBaseModuleLayer.configuration().findModule("java.base").isPresent());

Особым случаем является загрузочный уровень, создаваемый при запуске виртуальной машины Java. Уровень загрузки — это единственный уровень, содержащий модуль java.base .

3. Работа с дескриптором модуля

ModuleDescriptor описывает именованный модуль и определяет методы для получения каждого из его компонентов .

Объекты ModuleDescriptor являются неизменяемыми и безопасными для использования несколькими параллельными потоками.

Давайте начнем с рассмотрения того, как мы можем получить ModuleDescriptor.

3.1. Получение дескриптора модуля

Поскольку ModuleDescriptor тесно связан с модулем , его можно получить непосредственно из модуля:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

3.2. Создание дескриптора модуля

Также можно создать дескриптор модуля, используя класс ModuleDescriptor.Builder или прочитав двоичную форму объявления модуля, module-info.class .

Давайте посмотрим, как мы создаем дескриптор модуля, используя API ModuleDescriptor.Builder :

ModuleDescriptor.Builder moduleBuilder = ModuleDescriptor
.newModule("foreach.base");

ModuleDescriptor moduleDescriptor = moduleBuilder.build();

assertThat(moduleDescriptor.name(), is("foreach.base"));

При этом мы создали обычный модуль, но если мы хотим создать открытый модуль или автоматический, мы можем соответственно использовать метод newOpenModule() или newAutomaticModule() .

3.3. Классификация модуля

Дескриптор модуля описывает обычный, открытый или автоматический модуль.

Благодаря методу, доступному в ModuleDescriptor , можно определить тип модуля:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

assertFalse(moduleDescriptor.isAutomatic());
assertFalse(moduleDescriptor.isOpen());

3.4. Получение требует

С помощью дескриптора модуля можно получить набор Requires , представляющих зависимости модуля.

Это возможно с помощью метода require() :

Set<Requires> javaBaseRequires = javaBaseModule.getDescriptor().requires();
Set<Requires> javaSqlRequires = javaSqlModule.getDescriptor().requires();

Set<String> javaSqlRequiresNames = javaSqlRequires.stream()
.map(Requires::name)
.collect(Collectors.toSet());

assertThat(javaBaseRequires, empty());
assertThat(javaSqlRequiresNames, hasItems("java.base", "java.xml", "java.logging"));

Все модули, кроме java . база , есть java . базовый модуль как зависимость .

Однако, если модуль является автоматическим модулем, набор зависимостей будет пустым, кроме java.base .

3.5. Получение предоставляет

С помощью метода Provides() можно получить список сервисов, предоставляемых модулем:

Set<Provides> javaBaseProvides = javaBaseModule.getDescriptor().provides();
Set<Provides> javaSqlProvides = javaSqlModule.getDescriptor().provides();

Set<String> javaBaseProvidesService = javaBaseProvides.stream()
.map(Provides::service)
.collect(Collectors.toSet());

assertThat(javaBaseProvidesService, hasItem("java.nio.file.spi.FileSystemProvider"));
assertThat(javaSqlProvides, empty());

3.6. Получение экспорта

С помощью метода exports() мы можем узнать, экспортируют ли модули пакеты и какие, в частности:

Set<Exports> javaSqlExports = javaSqlModule.getDescriptor().exports();

Set<String> javaSqlExportsSource = javaSqlExports.stream()
.map(Exports::source)
.collect(Collectors.toSet());

assertThat(javaSqlExportsSource, hasItems("java.sql", "javax.sql"));

Как частный случай, если модуль автоматический, набор экспортируемых пакетов будет пустым.

3.7. Получение использований

С помощью метода uses() можно получить набор служебных зависимостей модуля:

Set<String> javaSqlUses = javaSqlModule.getDescriptor().uses();

assertThat(javaSqlUses, hasItem("java.sql.Driver"));

В случае, если модуль автоматический, набор зависимостей будет пустым.

3.8. Получение открытий

Всякий раз, когда мы хотим получить список открытых пакетов модуля, мы можем использовать метод opens() :

Set<Opens> javaBaseUses = javaBaseModule.getDescriptor().opens();
Set<Opens> javaSqlUses = javaSqlModule.getDescriptor().opens();

assertThat(javaBaseUses, empty());
assertThat(javaSqlUses, empty());

Набор будет пустым, если модуль открытый или автоматический.

4. Работа с модулями

Работая с API модуля , помимо чтения информации из модуля, мы можем обновить определение модуля.

4.1. Добавление экспорта

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

Module updatedModule = module.addExports(
"com.foreach.java9.modules", javaSqlModule);

assertTrue(updatedModule.isExported("com.foreach.java9.modules"));

Это можно сделать только в том случае, если вызывающий модуль является модулем, членом которого является код.

В качестве примечания: нет никаких эффектов, если пакет уже экспортирован модулем или если модуль является открытым.

4.2. Добавление чтений

Когда мы хотим обновить модуль для чтения данного модуля, мы можем использовать метод addReads() :

Module updatedModule = module.addReads(javaSqlModule);

assertTrue(updatedModule.canRead(javaSqlModule));

Этот метод ничего не делает, если мы добавляем сам модуль, так как все модули читают сами себя.

Точно так же этот метод ничего не делает, если модуль безымянный или этот модуль уже читает другой.

4.3. Добавление открытий

Когда мы хотим обновить модуль, который открыл пакет, по крайней мере, до вызывающего модуля, мы можем использовать addOpens() , чтобы открыть пакет для другого модуля:

Module updatedModule = module.addOpens(
"com.foreach.java9.modules", javaSqlModule);

assertTrue(updatedModule.isOpen("com.foreach.java9.modules", javaSqlModule));

Этот метод не действует, если пакет уже открыт для данного модуля.

4.4. Добавление использования

Всякий раз, когда мы хотим обновить модуль, добавляя сервисную зависимость, мы выбираем метод addUses() :

Module updatedModule = module.addUses(Driver.class);

assertTrue(updatedModule.canUse(Driver.class));

Этот метод ничего не делает при вызове безымянного модуля или автоматического модуля.

5. Вывод

В этой статье мы рассмотрели использование java.lang.Module API, где узнали, как получить информацию о модуле, как использовать ModuleDescriptor для доступа к дополнительной информации о модуле и как манипулировать ею.

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