W3docs

Déclaration de module Java avec module-info.java

Déclarez un module Java avec module-info.java — requires, exports, opens, uses, provides.

Un module est déclaré dans un fichier source spécial, module-info.java, placé à la racine de l'arborescence source du module (à côté du package principal, et non à l'intérieur). Il se compile en module-info.class. Ce fichier ne contient aucun code ordinaire — seulement un bloc module listant des directives qui décrivent la frontière du module.

La structure du fichier

module com.acme.orders {
    requires com.acme.common;       // I depend on this module
    requires transitive java.sql;   // ...and so does anyone who requires me

    exports com.acme.orders.api;            // public to everyone
    exports com.acme.orders.spi to com.acme.web;  // public to one module only

    opens com.acme.orders.model;    // deep reflective access (e.g. for Jackson)

    uses com.acme.orders.PricingRule;            // I consume this service
    provides com.acme.orders.PricingRule          // I supply an implementation
        with com.acme.orders.StandardPricing;
}

Le nom du module (com.acme.orders) est un identifiant pointé, suivant la convention du préfixe DNS inversé des packages qu'il contient. Ce n'est pas un package — c'est son propre espace de noms, et deux modules ne peuvent pas partager un package.

requires — déclarer les dépendances

requires <module> signifie « j'ai besoin des packages exportés par ce module pour compiler et s'exécuter. » Le résolveur échoue au démarrage si un module requis est absent. Deux modificateurs importants :

  • requires transitiveréexporte la dépendance. Tout module qui vous requiert la lit automatiquement aussi. Utilisez-le lorsque les types d'un module requis apparaissent dans vos propres signatures publiques (une méthode retournant un java.sql.Connection oblige les appelants à voir java.sql).
  • requires static — une dépendance uniquement à la compilation, optionnelle à l'exécution (pour les processeurs d'annotations, les intégrations optionnelles).

java.base est requis implicitement ; vous ne l'écrivez jamais.

exports — déclarer votre API publique

exports <package> rend les types public de ce package visibles aux autres modules. Tout ce qui n'est pas exporté est fortement encapsulé — invisible même s'il est public. La forme qualifiée, exports <package> to <module>, <module>, restreint la visibilité à une liste d'autorisation nommée, utile pour les packages SPI partagés uniquement entre vos propres modules.

Notez que exports est par package, jamais récursif : exporter com.acme.api n'exporte pas com.acme.api.internal.

opens — autoriser la réflexion profonde

exports accorde un accès à la compilation aux membres public. Cela n'accorde pas d'accès réflexif aux membres non publics. Des frameworks comme Jackson, Hibernate et Spring utilisent setAccessible(true) pour atteindre les champs private — cela nécessite opens :

  • opens <package> — accorde un accès réflexif à l'exécution (y compris aux membres private) à tous les modules.
  • opens <package> to <module> — qualifié, uniquement aux modules nommés.
  • open module com.acme.orders { … } — ouvre chaque package (un outil de migration radical).

La distinction est importante : vous utilisez exports pour un package d'API, mais vous utilisez opens pour un package de classes de données sur lesquelles vous voulez qu'un sérialiseur effectue de la réflexion sans en faire partie de votre API à la compilation.

uses / provides — les services

Ces directives configurent le patron ServiceLoader : uses <Service> déclare que vous consommez une interface de service, et provides <Service> with <Impl> déclare une implémentation. Ils ont leur propre chapitre — voir Services de module — notez simplement ici qu'ils vivent dans le même descripteur.

Exemple pratique : construire un descripteur avec l'API

Vous écrivez normalement module-info.java et laissez javac produire le descripteur. Mais la même structure est disponible de manière programmatique via ModuleDescriptor.newModule(...), qui est un miroir fidèle de la syntaxe des directives — en construire un est la façon la plus claire de voir ce que chaque directive devient.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Les noms des méthodes du constructeur correspondent un à un aux directives : .requires(...), .exports(...), .opens(...), .uses(...), .provides(...). En relisant le résultat, le descripteur contient exactement les informations d'un module-info.java — preuve que le fichier est de la pure métadonnée, pas du code exécutable.
  • Le require de java.sql est imprimé avec un modificateur [TRANSITIVE] tandis que com.acme.common est imprimé sans. Ce modificateur est ce qui réexporte java.sql aux modules en aval ; le require simple garde la dépendance privée à ce module.
  • Les deux exports sont imprimés différemment : com.acme.orders.api comme « (to all) » et com.acme.orders.spi comme « to [com.acme.web] ». Un export qualifié porte sa liste d'autorisation cible dans le descripteur — le résolveur l'applique, donc aucun autre module ne peut lire le package SPI.
  • opens apparaît dans sa propre section, séparée des exports. Le descripteur maintient l'exposition à la compilation et l'accès réflexif à l'exécution comme des faits distincts, c'est pourquoi un sérialiseur a besoin de opens même si le package est déjà exporté.
  • uses et provides sont enregistrés juste à côté du reste — les déclarations de service font partie de la frontière du module, pas un fichier de configuration séparé comme c'était le cas sur le classpath (META-INF/services). Services de module transforme ces directives en un ServiceLoader fonctionnel.

Erreurs courantes

  • Placer module-info.java à l'intérieur d'un répertoire de package — il doit se trouver à la racine source.
  • Confondre exports (à la compilation, membres publics) avec opens (à l'exécution, tous les membres). Une JsonMappingException concernant un champ inaccessible signifie presque toujours un opens manquant.
  • Oublier requires transitive lorsque vos méthodes publiques exposent les types d'un autre module, obligeant chaque appelant à ajouter le require manuellement.

Le chapitre suivant, Types de module, revient sur les trois types de module — nommé, automatique et non nommé — et comment une application partiellement modulaire continue de fonctionner pendant que vous migrez vers les modules. Pour une vue d'ensemble de la raison d'être du système de modules, voir l'introduction aux modules.

Pratique

Pratique
Un module 'com.acme.orders' possède une méthode publique qui retourne un 'java.sql.Connection'. Les appelants d'autres modules échouent à compiler car ils ne peuvent pas voir le type 'java.sql.Connection', même s'ils utilisent déjà 'requires com.acme.orders'. Quelle directive dans 'com.acme.orders' résout ce problème sans obliger chaque appelant à ajouter 'requires java.sql' lui-même ?
Un module 'com.acme.orders' possède une méthode publique qui retourne un 'java.sql.Connection'. Les appelants d'autres modules échouent à compiler car ils ne peuvent pas voir le type 'java.sql.Connection', même s'ils utilisent déjà 'requires com.acme.orders'. Quelle directive dans 'com.acme.orders' résout ce problème sans obliger chaque appelant à ajouter 'requires java.sql' lui-même ?
Was this page helpful?