Introduzione ai Moduli Java (JPMS)
Cosa sono i moduli in Java, i problemi che JPMS risolve e il suo rapporto con il classpath.
Il Java Platform Module System (JPMS), introdotto in Java 9, aggiunge un livello superiore ai package. Un modulo è un gruppo di package con un nome e una descrizione propria, che dichiara esplicitamente due cose: cosa richiede da altri moduli e cosa offre ad essi. Questa dichiarazione risiede in un unico file, module-info.java, alla radice del modulo. Questa parte del libro lo esamina nel dettaglio; questo capitolo spiega perché esiste.
Il problema che JPMS risolve: il classpath
Prima di Java 9, ogni JAR veniva aggiunto a un unico classpath piatto. Questa soluzione presentava problemi cronici:
- Nessuna incapsulazione. Ogni classe
publicin ogni JAR era raggiungibile da chiunque. Una classe pensata solo come helper interno (sun.misc.Unsafe,com.example.internal.*) poteva essere usata da chiunque, quindi non poteva mai essere modificata in modo sicuro. - Nessuna dipendenza dichiarata. Un JAR non dichiarava mai quali altri JAR richiedeva. Si scopriva una dipendenza mancante solo quando un
NoClassDefFoundErroresplodeva a runtime — possibilmente in produzione. - JAR hell. Due JAR che fornivano lo stesso package, o due versioni della stessa libreria, venivano uniti silenziosamente nell'ordine del classpath. Vinceva la classe caricata per prima.
I moduli affrontano tutti e tre i problemi: un modulo nasconde ogni package che non export esplicitamente, dichiara ogni modulo che requires, e la JVM verifica l'intero grafo all'avvio — moduli mancanti o duplicati falliscono rapidamente.
Moduli, package e JAR
I tre concetti sono facili da confondere:
| Concetto | Cosa raggruppa | Regola di visibilità |
|---|---|---|
| Package | classi | public/protected/package-private all'interno del JAR |
| JAR | package + risorse | tutto ciò che è public è visibile sul classpath |
| Modulo | package | solo i package exported sono visibili agli altri moduli |
Un modulo viene solitamente distribuito come JAR (un "modular JAR" — un JAR ordinario con un module-info.class alla radice). La differenza sta nel descrittore: posizionare il JAR sul classpath fa ignorare le regole; posizionarlo sul module path fa applicare JPMS.
Incapsulazione forte, in una frase
La regola fondamentale: un package è invisibile agli altri moduli a meno che il suo modulo non lo exports — anche se le sue classi sono public. public ora significa "accessibile al codice che può leggere questo package", e leggere un package richiede sia exports che requires. Ecco perché il JDK stesso ha finalmente potuto nascondere i propri internals: java.base esporta java.util ma non jdk.internal.misc.
Anche il JDK è modulare
Da Java 9 il JDK è suddiviso in circa 70 moduli (java.base, java.sql, java.xml, java.net.http, …). java.base è speciale: è implicitamente richiesto da ogni modulo e contiene i fondamentali del linguaggio (java.lang, java.util, java.io). Ogni classe che hai mai usato risiede in uno di questi moduli — come dimostra l'esempio pratico qui sotto.
Un esempio pratico: ispezione dei moduli a runtime
Non è necessario scrivere un modulo per vedere i moduli: la Module API a runtime riporta il modulo di qualsiasi classe. Questo programma chiede a diverse classi a quale modulo appartengono, verifica il proprio modulo e dà un'occhiata al boot layer avviato dalla JVM.
Cosa ricavare dall'esecuzione:
String,ArrayListeHttpClienthanno riportato rispettivamentejava.base,java.baseejava.net.http. Ogni classe appartiene esattamente a un modulo, egetModule()indica quale — i tipi del linguaggio risiedono tutti injava.base, mentreHttpClientè nel proprio modulo per cui sarebbe necessariorequires java.net.http.- La classe del programma ha riportato
isNamed() == falsee ungetName()pari anull. Il codice eseguito dal classpath finisce nel modulo senza nome, un contenitore di compatibilità che non richiede nulla esplicitamente e legge ogni altro modulo. Ecco perché i programmi sul classpath continuano a compilarsi ed eseguirsi invariati su Java 9+. ModuleLayer.boot()ha esposto il grafo dei moduli risolti dalla JVM all'avvio — contare ijava.*dimostra che il JDK è davvero suddiviso in molti moduli, non in un monolite.java.basenon è aperto (isOpen() == false) ma esporta molti package; esponejava.langejava.utila tutti mantenendo nascostijdk.internal.*. Esportare un package e aprire un modulo sono due opzioni distinte — un capitolo successivo approfondisceopens.- Niente di tutto ciò ha richiesto un
module-info.java. La Module API è metadato riflessivo disponibile per qualsiasi programma; scrivere il proprio modulo (prossimo capitolo) è ciò che permette a te di dichiarare queste regole invece di limitarti a osservare quelle del JDK.
Cosa copre il resto di questa parte
module-info.java— le direttive:requires,exports,opens,uses,provides.- Tipi di modulo — moduli con nome, automatici e senza nome, e come si combinano.
- Servizi — disaccoppiare un'interfaccia dalla sua implementazione con
uses/provideseServiceLoader. - Migrazione — spostare un'applicazione esistente dal classpath al module path senza una riscrittura completa.
I moduli sono facoltativi: un'applicazione Java può funzionare indefinitamente sul classpath. Ma comprenderli spiega il JDK moderno, sblocca i runtime personalizzati con jlink e fornisce alle librerie una vera incapsulazione. Il prossimo capitolo, la dichiarazione module-info.java, scrive il descrittore che rende un modulo tale.