Tipi di modulo Java
Moduli named, automatic e unnamed in Java e come interagiscono durante la compilazione e l'esecuzione.
Il Java Platform Module System (JPMS) riconosce tre tipi di modulo. Solo uno è il "vero" modulo che si scrive; gli altri due esistono affinché i milioni di JAR precedenti a Java 9 continuino a funzionare. Capire quale tipo diventa un determinato JAR — e che dipende interamente da dove lo si posiziona — è la chiave per migrare senza problemi. Questa pagina definisce tutti e tre i tipi, mostra le regole di accesso tra di essi e dimostra le categorie con un programma eseguibile.
Moduli named
Un modulo named (esplicito) è quello con un module-info.class, posizionato sul module path (--module-path / -p). È il cittadino di prima classe:
- Ha un nome proveniente dal suo descrittore.
- Legge solo i moduli che dichiara con
requires. - Espone solo i pacchetti che dichiara con
exports.
Questo è il modulo fortemente incapsulato descritto nel capitolo sulla dichiarazione del modulo. Tutto ciò che JPMS promette — dipendenze dichiarate, interni nascosti, risoluzione fail-fast — si applica ai moduli named.
Moduli automatic
Un modulo automatic è un JAR semplice (senza module-info) posizionato sul module path. JPMS lo racchiude in un modulo affinché i moduli named possano dichiararlo con requires durante la migrazione — senza dover attendere che l'autore della libreria aggiunga un descrittore. Un modulo automatic:
- Ottiene un nome derivato dal nome del file JAR (es.
guava-32.1.jar→guava), a meno che il manifest del JAR non impostiAutomatic-Module-Name. - Esporta ogni pacchetto — non ha direttiva
exports, quindi tutti i suoi pacchetti sono aperti a tutti. - Legge ogni altro modulo, incluso il modulo unnamed, così può ancora vedere i JAR sul classpath.
È un ponte: permette di iniziare a scrivere moduli named che dipendono da librerie non ancora modularizzate. Il costo è che rinuncia completamente all'incapsulamento, e il suo nome derivato automaticamente può cambiare se il JAR viene rinominato — ecco perché Automatic-Module-Name nel manifest è la scelta responsabile per una libreria.
Il modulo unnamed
Il modulo unnamed è il raccoglitore universale per il classpath. Ogni classe caricata dal classpath appartiene al modulo unnamed del suo class loader. Esso:
- Non ha nome (
getName()restituiscenull,isNamed()èfalse). - Legge ogni altro modulo nel sistema.
- Esporta tutti i suoi pacchetti agli altri moduli unnamed/automatic.
Esiste però un muro deliberatamente unidirezionale: un modulo named non può dichiarare requires per il modulo unnamed. Non è possibile nominarlo, quindi non è possibile dipendere da esso. Questa è la regola che impone l'ordine di migrazione — un modulo named può dipendere solo da altri moduli named o automatic, mai da codice sul classpath grezzo.
La matrice di accesso
Chi può leggere chi si riduce a una piccola tabella:
| Da ↓ / A → | Named | Automatic | Unnamed |
|---|---|---|---|
| Named | solo con requires | solo con requires | mai |
| Automatic | sì | sì | sì |
| Unnamed | sì | sì | sì |
L'unica cella restrittiva — il codice named non può raggiungere il codice unnamed — è l'intera ragione per cui si migra dal basso verso l'alto (trattato nel prossimo capitolo).
Un esempio pratico: identificare il tipo di un modulo a runtime
La Module API indica, per qualsiasi classe, se il suo modulo è named e se è stato sintetizzato automaticamente. Questo programma ispeziona tre riferimenti — la propria classe (classpath → unnamed), un tipo JDK (named), e riporta il boot layer — per rendere concreti i concetti.
Cosa ricavare dall'esecuzione:
- La classe del programma è classificata come UNNAMED con nome
null, mentrejava.util.ListeHttpClientsono classificati come NAMED (java.base,java.net.http). Eseguito dal classpath, il tuo codice è sempre unnamed; il JDK è sempre un insieme di moduli named. Il tipo di un modulo è determinato da come è stato caricato, non da nulla nella classe stessa. java.base.canRead(self)ha restituitofalsemaself.canRead(java.base)ha restituitotrue. Questo è il muro unidirezionale in azione: il modulo unnamed legge tutto, ma nessun modulo named legge il modulo unnamed. Questa asimmetria è precisamente il motivo per cui il codice named non può dichiararerequiresper il codice classpath.classify()ha distinto automatic da named tramitedescriptor.isAutomatic(). Non si vedràtruequi (nulla è stato posizionato sul module path come JAR semplice), ma il controllo è esattamente il modo in cui gli strumenti segnalano un modulo automatic — un vero oggetto modulo con un descrittore sintetizzato e completamente aperto.isExported("java.util")ha restituitotruemaisExported("jdk.internal.misc")ha restituitofalse, anche se entrambi sono pacchetti reali all'interno dijava.base. La direttivaexportsdi un modulo named è una lista di permessi; i pacchetti non esportati (o esportati solo in modo qualificato) sono invisibili al codice esterno anche sepublic. Il modulo unnamed, al contrario, esporta tutto ciò che contiene.- Non è stato necessario alcun
module-info.javaper osservare nulla di tutto questo. Le tre categorie sono fatti a runtime sul modo in cui una classe è stata caricata, egetModule()piùgetDescriptor()le espongono — le stesse chiamate su cui si basano gli strumenti di migrazione per capire con cosa stanno lavorando.
Perché esistono tre tipi
I due tipi di compatibilità — automatic e unnamed — fanno sì che Java 9+ esegua applicazioni Java 8 non modificate. Si opta per un forte incapsulamento un JAR alla volta: si lascia tutto sul classpath (tutto unnamed) e niente cambia; si sposta una libreria sul module path senza un descrittore e diventa automatic; si aggiunge un module-info.java e diventa named. Successivamente, i servizi del modulo mostrano il meccanismo uses/provides che disaccoppia i moduli, e la migrazione dei moduli guida un progetto reale attraverso questi tre stati.