Java moodulisüsteem (JPMS)
Sissejuhatus
Eelmises peatükis tutvusime täpsemalt IntelliJ moodulitega ehk kuidas kuidas projekte paremini strukturiseerida. Käesolevas peatükis tutvume ühe teise süsteemiga, mille nimes on ka sõna "moodul" ning on osa Javast: Java Platform Module System (JPMS). See on osa Javast alates Java 9-st.
JPMS on Javasse sisse ehitatud süsteem, millega saab kindlaks määrata, millist osa koodist on võimalik kolmandatel osapooltel kasutada ning millest need sõltuvad.
Sisuliselt see on kapseldamine aga veelgi kõrgemal tasemel.
Selle asemel, et klassile määrata ligipääs public või private kaudu, haldab JPMS ligipääsu moodulite vahel pakettide tasemel.
Millist probleemi JPMS lahendab
Enne Java 9 oli kogu classpath'il olev kood nähtav ülejäänud koodile. Puudus võimalus öelda, et "see pakett on selle teegi sisemine osa ning seda ei tohiks väljaspoolt kasutada".
Näiteks kui kirjutasid endale abiklasside paki com.example.internal, siis miski ei takistanud teistel arendajatel seda kasutada, kuigi see ei olnud selleks ette nähtud.
Suures mastaabis tekkis sellest korralik probleem:
- JDK sisemisi API-sid kasutasid kolmandate osapoolte teegid, mis tegi Java muutmise või täiustamise keeruliseks ilma olemasolevat koodi lõhkumata.
- Suurtel rakendustel puudusid jõustatud piirid oma komponentide vahel.
JPMS loodi selle probleemi lahendamiseks, muutes sõltuvused ja avalikud API-d selgesõnaliseks ning kompilaatori poolt kontrollitavaks.
module-info.java fail
JPMS konfigureeritakse läbi module-info.java faili, mis asub lähtekoodi juurkaustas (mitte pakkides).
Näiteks:
src/
├── module-info.java
└── university/
├── students/
│ └── Student.java
└── util/
└── IdGenerator.java
Minimaalne module-info.java fail näeks välja nii:
module university.app {
}
See deklareerib mooduli nimega university.app.
Ilma täiendavate deklaratsioonideta ei ole miski selle sees välistele moodulitele ligipääsetav.
Märksõnad
exports
exports märksõna annab märku, et antud pakk on nähtav teistele moodulitele.
Lähtume eelnevast näitest, kus university pakis on kaks alam-pakki.
Kui university.students pakk export märksõnaga ära märkida:
module university.app {
exports university.students; // this package is public API
// university.util stays hidden
}
siis see pakk muutub teistele pakkidele avalikuks ehk saab vabalt kasutada.
university.util pakk jääks suletuks ning ainult moodulisiseseks kasutuseks, isegi kui klassid antud pakis on avalikud.
requires
requires märksõna kaudu saab mooduli panna sõltuma mõnest teisest moodulist.
Näiteks:
module university.app {
exports university.students;
requires java.logging; // depend on a JDK module
}
Kui antud moodul(id) pole kompileerimise ajal saadaval, siis kompileerimine ebaõnnestub. Sõltuvused on selgesõnaliselt määratletud ja kontrollitud enne kompileerimise alustamist. Kompilaator ei tee vaikimisi eeldusi sõltuvuste olemasolu kohta.
requires transitive
Tavapärane requires tähendab, et moodul sõltub teisest moodulist, kuid see sõltuvus ei kandu edasi.
requires transitive tähendab, et kui moodul A sõltub moodulist B ning märgib selle requires transitive abil, siis kõik moodulid, mis sõltuvad A-st, saavad automaatselt ligipääsu ka B-le.
Näiteks:
module university.app {
requires transitive university.api;
}
Kui mõni teine moodul kasutaks requires university.app;, siis university.api sõltuvust ei pea eraldi deklaleerima.
See tuleb esimesega automaatselt kaasa.
Seda kasutatakse tavaliselt siis, kui üks moodul on osa avalikust API-st ja selle sõltuvused peaksid olema nähtavad ka edasi.
opens
exports teeb paki teistele moodulitele kompileerimise ajal kättesaadavaks.
opens on teistsugune: see võimaldab käitusajal paketile ligipääsu refleksiooni kaudu.
module university.app {
opens university.students;
}
See ei tee pakki tavaliseks kasutamiseks avalikuks, vaid lubab näiteks raamistikul (nt Spring, Hibernate) ligi pääseda klasside väljadele ja konstruktoritele refleksiooni abil.
opens on vajalik eelkõige siis, kui kasutatakse teeke, mis loovad objekte või loevad andmeid refleksiooni kaudu.
uses
uses märksõna kasutatakse koos Java ServiceLoader mehhanismiga.
Sellega annab moodul märku, et ta kasutab mingit liidest, kuid ei määra konkreetset teostust.
module university.app {
uses university.spi.IdGenerator;
}
See tähendab, et moodul eeldab, et käitusajal on saadaval mõni moodul, mis pakub IdGenerator liidese teostust.
uses ei loo otsest sõltuvust konkreetsele implementatsioonile.
Selle asemel otsitakse sobivad teostused üles dünaamiliselt ServiceLoader abil.
Näide Javast endast
JDK ise on täielikult modulariseeritud.
Selles saab veenduda, kui käivitada terminalis käsk java --list-modules, mis kuvab kõik moodulid, millest Java standardteek koosneb.
Võtame näiteks java.sql mooduli.
Selle module-info.java näeb välja selline:
module java.sql {
requires transitive java.logging;
requires transitive java.xml;
exports java.sql;
exports javax.sql;
exports javax.transaction.xa;
uses java.sql.Driver;
}