Java 9 java.lang.Module API

Java 9 java.lang.Module API

1. Вступление

СледуяA Guide to Java 9 Modularity, в этой статье мы собираемся изучить APIjava.lang.Module, который был представлен вместе с системой модулей платформы Java.

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

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

КлассModule представляет как именованные, так и безымянные модули. Named modules have a name and are constructed by the Java Virtual Machine when it creates a module layer,, используя в качестве определения граф модулей.

У безымянного модуля нет имени, и есть одно для каждогоClassLoader.All types that aren’t in a named module are members of the unnamed module related to their class loader.

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

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

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

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

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

Class hashMapClass = HashMap.class;
Module javaBaseModule = hashMapClass.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 частью именованного модуля:

Class personClass = Person.class;
Module module = personClass.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. Аннотации

Так же, как и для пакетовit’s possible to retrieve the annotations that are present in the module using the getAnnotations() method.

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

Посмотрим, сколько аннотаций присутствует в модуле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("jaa.base").isPresent());
assertThat(javaBaseModuleLayer.configuration().modules().size(), is(78));

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

3. Работа сModuleDescriptor

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

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

Начнем с того, что посмотрим, как получитьModuleDescriptor.

3.1. ПолучениеModuleDescriptor

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

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

3.2. СозданиеModuleDescriptor

It’s also possible to create a module descriptor using the ModuleDescriptor.Builder class или путем чтения двоичной формы объявления модуляmodule-info.class.

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

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

ModuleDescriptor moduleDescriptor = moduleBuilder.build();

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

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

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

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

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

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

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

3.4. Требуется получение

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

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

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

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

assertThat(javaBaseRequires, empty());
assertThat(javaSqlRequires.size(), is(3));
assertThat(
  javaSqlRequiresNames,
  containsInAnyOrder("java.base", "java.xml", "java.logging")
);

All modules, except java.base, have the java.base module as a dependency.

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

3.5. Получение обеспечивает

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

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

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

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

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

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

Set javaBaseExports = javaBaseModule.getDescriptor().exports();
Set javaSqlExports = javaSqlModule.getDescriptor().exports();

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

assertThat(javaBaseExports.size(), is(108));
assertThat(javaSqlExports.size(), is(3));
assertThat(
  javaSqlExportsSource,
  containsInAnyOrder("java.sql","javax.transaction.xa", "javax.sql")
);

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

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

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

Set javaBaseUses = javaBaseModule.getDescriptor().uses();
Set javaSqlUses = javaSqlModule.getDescriptor().uses();

assertThat(javaBaseUses.size(), is(34));
assertThat(javaSqlUses, contains("java.sql.Driver"));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Module updatedModule = module.addReads(javaSqlModule);

assertTrue(updatedModule.canRead(javaSqlModule));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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