W3docs

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 public in 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 NoClassDefFoundError esplodeva 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:

ConcettoCosa raggruppaRegola di visibilità
Packageclassipublic/protected/package-private all'interno del JAR
JARpackage + risorsetutto ciò che è public è visibile sul classpath
Modulopackagesolo 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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • String, ArrayList e HttpClient hanno riportato rispettivamente java.base, java.base e java.net.http. Ogni classe appartiene esattamente a un modulo, e getModule() indica quale — i tipi del linguaggio risiedono tutti in java.base, mentre HttpClient è nel proprio modulo per cui sarebbe necessario requires java.net.http.
  • La classe del programma ha riportato isNamed() == false e un getName() pari a null. 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 i java.* dimostra che il JDK è davvero suddiviso in molti moduli, non in un monolite.
  • java.base non è aperto (isOpen() == false) ma esporta molti package; espone java.lang e java.util a tutti mantenendo nascosti jdk.internal.*. Esportare un package e aprire un modulo sono due opzioni distinte — un capitolo successivo approfondisce opens.
  • 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/provides e ServiceLoader.
  • 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.

Pratica

Pratica
Una libreria JAR contiene una classe 'public' nel package 'com.acme.internal' che gli autori intendono usare solo internamente alla libreria. Il JAR è costruito come modular JAR il cui 'module-info.java' esporta 'com.acme.api' ma non 'com.acme.internal'. Cosa succede quando questo JAR viene posizionato sul MODULE PATH e un altro modulo tenta di importare 'com.acme.internal.Helper'?
Una libreria JAR contiene una classe 'public' nel package 'com.acme.internal' che gli autori intendono usare solo internamente alla libreria. Il JAR è costruito come modular JAR il cui 'module-info.java' esporta 'com.acme.api' ma non 'com.acme.internal'. Cosa succede quando questo JAR viene posizionato sul MODULE PATH e un altro modulo tenta di importare 'com.acme.internal.Helper'?
Was this page helpful?