Java 9 API java.lang.Module

API java.lang.Module Java 9

1. introduction

À la suite deA Guide to Java 9 Modularity, dans cet article, nous allons explorer l'APIjava.lang.Module qui a été introduite avec le Java Platform Module System.

Cette API fournit un moyen d'accéder à un module par programme, de récupérer des informations spécifiques à partir d'un module, et généralement de travailler avec lui et sesModuleDescriptor.

2. Lire les informations du module

La classeModule représente à la fois les modules nommés et non nommés. Named modules have a name and are constructed by the Java Virtual Machine when it creates a module layer, utilisant un graphe de modules comme définition.

Un module sans nom n’a pas de nom et il y en a un pour chaqueClassLoader.All types that aren’t in a named module are members of the unnamed module related to their class loader.

La partie intéressante de la classeModule est qu'elle expose des méthodes qui nous permettent de récupérer des informations du module, comme le nom du module, le chargeur de classe du module et les packages dans le module.

Voyons comment il est possible de savoir si un module est nommé ou non.

2.1. Nommé ou sans nom

En utilisant la méthodeisNamed(), nous pouvons identifier si un module est nommé ou non.

Voyons comment nous pouvons voir si une classe donnée, commeHashMap, fait partie d'un module nommé et comment nous pouvons récupérer son nom:

Class hashMapClass = HashMap.class;
Module javaBaseModule = hashMapClass.getModule();

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

Définissons maintenant une classePerson:

public class Person {
    private String name;

    // constructor, getters and setters
}

De la même manière, comme nous l'avons fait pour la classeHashMap, nous pouvons vérifier si la classePerson fait partie d'un module nommé:

Class personClass = Person.class;
Module module = personClass.getModule();

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

2.2. Paquets

Lorsque vous travaillez avec un module, il peut être important de savoir quels packages sont disponibles dans le module.

Voyons comment nous pouvons vérifier si un package donné, par exemplejava.lang.annotation, est contenu dans un module donné:

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

2.3. Annotations

De la même manière, que pour les packages,it’s possible to retrieve the annotations that are present in the module using the getAnnotations() method.

S'il n'y a pas d'annotations présentes dans un module nommé, la méthode retournera un tableau vide.

Voyons combien d'annotations sont présentes dans le modulejava.base:

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

Lorsqu'elle est appelée sur un module sans nom, la méthodegetAnnotations() renverra un tableau vide.

2.4. ClassLoader

Grâce à la méthodegetClassLoader() disponible dans la classeModule, nous pouvons récupérer lesClassLoader pour un module donné:

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

2.5. Couche

Une autre information précieuse qui pourrait être extraite d'un module est leModuleLayer, qui représente une couche de modules dans la machine virtuelle Java.

Une couche de module informe la JVM des classes qui peuvent être chargées à partir des modules. De cette manière, la JVM sait exactement de quel module chaque classe appartient.

UnModuleLayer contient des informations relatives à sa configuration, la couche parente et l'ensemble des modules disponibles dans la couche.

Voyons comment récupérer lesModuleLayer d'un module donné:

ModuleLayer javaBaseModuleLayer = javaBaseModule.getLayer();

Une fois que nous avons récupéré lesModuleLayer, nous pouvons accéder à ses informations:

assertTrue(javaBaseModuleLayer.configuration().findModule("jaa.base").isPresent());
assertThat(javaBaseModuleLayer.configuration().modules().size(), is(78));

La couche de démarrage, créée au démarrage de Java Virtual Machine, constitue un cas particulier. La couche de démarrage est la seule couche qui contient le modulejava.base.

3. Traiter avecModuleDescriptor

UnModuleDescriptor décrit un module nommé et définit des méthodes pour obtenir chacun de ses composants.

Les objetsModuleDescriptor sont immuables et sûrs pour une utilisation par plusieurs threads simultanés.

Commençons par voir comment nous pouvons récupérer unModuleDescriptor.

3.1. Récupération d'unModuleDescriptor

Puisque leModuleDescriptor est étroitement connecté à unModule, il est possible de le récupérer directement à partir d'unModule:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

3.2. Créer unModuleDescriptor

It’s also possible to create a module descriptor using the ModuleDescriptor.Builder class ou en lisant la forme binaire d'une déclaration de module, lesmodule-info.class.

Voyons comment nous créons un descripteur de module à l'aide de l'APIModuleDescriptor.Builder:

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

ModuleDescriptor moduleDescriptor = moduleBuilder.build();

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

Avec cela, nous avons créé un module normal mais dans le cas où nous voulons créer un module ouvert ou automatique, nous pouvons respectivement utiliser la méthodenewOpenModule() ounewAutomaticModule().

3.3. Classer un module

Un descripteur de module décrit un module normal, ouvert ou automatique.

Grâce à la méthode disponible dans lesModuleDescriptor, il est possible d’identifier le type de module:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

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

3.4. La récupération nécessite

Avec un descripteur de module, il est possible de récupérer l’ensemble deRequires, représentant les dépendances du module.

Ceci est possible en utilisant la méthoderequires():

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.

Cependant, si le module est un module automatique, l'ensemble des dépendances sera vide à l'exception de celui dejava.base.

3.5. Récupération fournit

Avec la méthodeprovides(), il est possible de récupérer la liste des services fournis par le module:

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. Récupération des exportations

En utilisant la méthodeexports(), on peut savoir si les modules exportent des packages et lesquels en particulier:

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")
);

Dans des cas particuliers, si le module est automatique, l'ensemble des packages exportés sera vide.

3.7. Récupération des utilisations

Avec la méthodeuses(), il est possible de récupérer l’ensemble des dépendances de service du module:

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

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

Dans le cas où le module est automatique, l'ensemble des dépendances sera vide.

3.8. Récupération des ouvertures

Chaque fois que nous voulons récupérer la liste des packages ouverts d'un module, nous pouvons utiliser la méthodeopens():

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

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

L'ensemble sera vide si le module est ouvert ou automatique.

4. Gestion des modules

En travaillant avec l'APIModule, autre que la lecture des informations du module, nous pouvons mettre à jour une définition de module.

4.1. Ajouter des exportations

Voyons comment nous pouvons mettre à jour un module, en exportant le package donné à partir d'un module donné:

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

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

Cela ne peut être fait que si le module de l’appelant est le module dont le code est membre.

En remarque, il n'y a aucun effet si le package est déjà exporté par le module ou si le module est ouvert.

4.2. Ajout de lectures

Lorsque nous voulons mettre à jour un module pour lire un module donné, nous pouvons utiliser la méthodeaddReads():

Module updatedModule = module.addReads(javaSqlModule);

assertTrue(updatedModule.canRead(javaSqlModule));

Cette méthode ne fait rien si nous ajoutons le module lui-même puisque tous les modules se lisent eux-mêmes.

De la même manière, cette méthode ne fait rien si le module est un module sans nom ou si ce module lit déjà l'autre.

4.3. Ajout d'ouvertures

Lorsque nous voulons mettre à jour un module qui a ouvert un package vers au moins le module appelant, nous pouvons utiliseraddOpens() pour ouvrir le package vers un autre module:

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

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

Cette méthode n'a aucun effet si le package est déjà ouvert sur le module donné.

4.4. Ajout d'utilisations

Chaque fois que nous voulons mettre à jour un module en ajoutant une dépendance de service, la méthodeaddUses() est notre choix:

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

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

Cette méthode ne fait rien lorsqu'elle est appelée sur un module non nommé ou un module automatique.

5. Conclusion

Dans cet article, nous avons exploré l'utilisation de l'APIjava.lang.Module où nous avons appris à récupérer les informations d'un module, comment utiliser unModuleDescriptor pour accéder à des informations supplémentaires concernant un module et comment le manipuler.

Comme toujours, tous les exemples de code de cet article peuvent être trouvésover on GitHub.