1. Che cos’è module-info.java e dove si trova?
Un modulo in Java inizia sempre dal file module-info.java. È una sorta di «passaporto» del modulo, dove dichiari il suo nome, i pacchetti esportati e le dipendenze da altri moduli.
Dove cercarlo?
Il file module-info.java deve trovarsi alla radice della cartella sorgenti del tuo modulo. Ad esempio, se la struttura del progetto è la seguente:
project-root/
└── src/
└── my.module.name/
├── module-info.java
└── com/
└── example/
└── api/
└── MyClass.java
Qui my.module.name è il nome del modulo (sulle regole di denominazione più avanti).
Senza questo file il modulo non è proprio un modulo!
Se manca — è un normale «vecchio» progetto Java, anche se pieno di cartelle e classi.
2. Sintassi di module-info.java: elementi fondamentali
Diamo subito un’occhiata a un esempio del file più semplice — non è nulla di spaventoso, davvero!
module my.module.name {
exports com.example.api;
requires java.sql;
}
Analizziamolo pezzo per pezzo:
module my.module.name { ... }
Questa è la dichiarazione di un modulo di nome my.module.name. Il nome del modulo è un identificatore univoco, di solito coincide con il package radice (per esempio, com.example.app).
Curiosità: se chiami il modulo java.base, il compilatore non sarà affatto contento. Meglio non provare a sostituire i moduli standard.
exports com.example.api;
Questa riga dice: «Io, modulo, sono pronto a condividere tutto ciò che si trova nel package com.example.api». Tutto ciò che si trova in questo package ed è marcato come public sarà visibile agli altri moduli. Tutto il resto — riservato all’uso interno.
requires java.sql;
Qui dichiariamo onestamente al compilatore: «Per funzionare ho bisogno del modulo standard java.sql». Senza di ciò il compilatore non ci permetterà di usare le classi di questo modulo.
Parole chiave aggiuntive (per completezza)
- opens <package>; — apre il package alla reflection (per esempio, per librerie di serializzazione come Jackson).
- uses <service-interface>; — indica che il modulo utilizza un certo servizio (interfaccia).
- provides <service-interface> with <implementation-class>; — dichiara che il modulo fornisce un’implementazione del servizio.
In questa lezione ci concentreremo su exports e requires — servono nella stragrande maggioranza dei progetti didattici e di produzione.
3. Esempi di module-info.java
Esempio 1. Modulo minimale
module com.example.hello {
exports com.example.hello.api;
}
- Questo modulo esporta solo il package com.example.hello.api.
- Tutto ciò che si trova, ad esempio, in com.example.hello.internal sarà nascosto agli altri moduli, anche se ci sono classi public.
Esempio 2. Modulo con dipendenza
module com.example.dbclient {
exports com.example.db.api;
requires java.sql;
}
Possiamo usare JDBC, ma solo perché abbiamo dichiarato esplicitamente la dipendenza da java.sql.
Esempio 3. Più package esportati
module com.example.library {
exports com.example.library.api;
exports com.example.library.utils;
}
Si possono esportare quanti package si vuole (ma non conviene esportare tutto indiscriminatamente — è proprio questo il senso dei moduli!).
4. Limitazioni e regole
Nome del modulo
- Di solito coincide con il package radice (per esempio, com.example.app).
- Non deve coincidere con i nomi dei moduli standard (java.base, java.sql, ecc.).
- Non deve contenere spazi, caratteri speciali, iniziare con una cifra, ecc.
- Raccomandazione: usa il nome di dominio rovesciato della tua organizzazione o del progetto per evitare conflitti.
Un modulo — un solo module-info.java
In un modulo può esserci un solo file di questo tipo. Se ce ne sono due — il compilatore ti farà una «scenata modulare».
Un package può essere esportato da un solo modulo
Lo stesso package non può essere esportato da due moduli diversi. È come avere due passaporti con lo stesso nome — lo Stato non lo approverebbe.
Package all’interno del modulo
Si possono esportare solo i package che esistono realmente nella struttura dei sorgenti di quel modulo. L’esportazione di un package inesistente comporta un errore di compilazione.
5. Pratica: creiamo module-info.java nel progetto
Supponiamo di avere un progetto semplice con il seguente contenuto:
project-root/
└── src/
└── com.example.greetings/
├── module-info.java
└── com/
└── example/
└── greetings/
├── api/
│ └── Greeter.java
└── internal/
└── SecretSauce.java
Passo 1. Creiamo module-info.java
module com.example.greetings {
exports com.example.greetings.api;
}
Passo 2. Proviamo a usare una classe dal package internal in un altro modulo
Supponiamo di avere un secondo modulo com.example.app che vuole accedere a SecretSauce:
module com.example.app {
requires com.example.greetings;
}
import com.example.greetings.internal.SecretSauce; // ERRORE!
Risultato:
Il compilatore dirà: «Il package com.example.greetings.internal non è esportato dal modulo com.example.greetings». Anche se la classe SecretSauce è public, non è accessibile agli altri moduli.
Questa è la vera incapsulazione a livello di moduli!
Passo 3. Proviamo a non dichiarare requires
Se in com.example.app non scriviamo requires com.example.greetings; e proviamo a usare una classe da com.example.greetings.api, il compilatore genererà un errore:
package com.example.greetings.api is not visible
6. Errori tipici lavorando con module-info.java
Errore n. 1: il nome del modulo non corrisponde alla struttura del progetto.
Se chiami il modulo com.example.app ma la struttura delle cartelle è src/main/java/app, il compilatore non capirà cosa vuoi. Il nome del modulo di solito coincide con il package radice e le cartelle dovrebbero rifletterlo.
Errore n. 2: esportare tutto in blocco.
Bisogna esportare solo ciò che deve davvero essere visibile agli altri moduli. Non fare exports com.example; solo perché «è più semplice». Questo viola l’incapsulazione.
Errore n. 3: dimenticare di aggiungere requires.
Se usi classi da un altro modulo o dalla libreria standard (ad esempio, java.sql) ma ti dimentichi di dichiarare la dipendenza — otterrai un errore di compilazione.
Errore n. 4: package inesistente in exports.
Se scrivi exports com.example.foo; ma quel package non esiste — il compilatore dirà che stai «esportando aria».
Errore n. 5: classi public in un package non esportato.
Se una classe è dichiarata public ma si trova in un package che non è esportato, tale classe sarà visibile solo all’interno del modulo. Non è un errore, ma spesso sorprende i principianti.
Errore n. 6: più module-info.java in un solo modulo.
In un modulo deve esserci un solo file module-info.java. Se ce ne sono due — il compilatore non riuscirà a costruire il progetto.
GO TO FULL VERSION