Module bernama dideklarasikan lewat satu berkas khusus bernama module-info.java yang diletakkan di akar hierarki sumber module. Berkas ini adalah deskriptor module — daftar pernyataan ringkas yang dibaca compiler saat kompilasi dan JVM saat peluncuran. Isinya menentukan siapa boleh membaca siapa, package mana yang menjadi API publik, dan bagian mana yang dibuka untuk reflection. Pada Chapter ini kita akan membedah anatomi deskriptor secara utuh: dari requires, exports, opens, hingga uses dan provides, lalu membaca deskriptor module JDK sungguhan dari dalam program.
Anatomi module-info.java #
Bentuk lengkap deskriptor memuat lima kelompok direktif. Contoh berikut memakai nama fiktif com.toko.erm sebagai module aplikasi:
module com.toko.erm {
requires java.logging; // 1. module yang dibaca
requires transitive java.sql; // 2. module yang ikut dibaca pemakai kita
exports com.toko.erm.api; // 3. API publik module ini
exports com.toko.erm.spi to
com.toko.erm.plugin; // ...khusus untuk satu module
opens com.toko.erm.model; // 4. dibuka untuk reflection
uses com.toko.erm.spi.Alat; // 5. layanan yang kita konsumsi
provides com.toko.erm.spi.Alat // ...dan layanan yang kita pasok
with com.toko.erm.internal.AlatBawaan;
}Rangkuman perannya:
| Direktif | Arti singkat |
|---|---|
requires N | module ini membaca module N |
requires transitive N | pemakai module ini otomatis ikut membaca N |
exports P | package P dapat diakses module lain |
exports P to M | package P hanya untuk module M |
opens P | package P boleh diakses reflection dari luar |
uses S | module ini mencari implementasi layanan S lewat ServiceLoader |
provides S with I | module ini menyediakan implementasi I untuk layanan S |
Sekelompok package yang tidak muncul di exports mana pun otomatis menjadi internal — meskipun seluruh class-nya public, tidak ada module lain yang bisa mengimpornya.
requires dan requires transitive #
requires java.logging menyatakan: module ini membaca module java.logging, sehingga class-nya boleh memakai package yang diekspor java.logging. Kalimat “membaca” adalah istilah resmi JPMS — akses antarmodule hanya terjadi bila pengirim requires penerima.
Varian requires transitive menjawab masalah keterbacaan tidak langsung. Ketika API publik module kita, misalnya method yang mengembalikan tipe java.sql.Connection, menyebut tipe dari module lain, pemakai module kita sebenarnya juga butuh membaca java.sql. Tanpa transitive, setiap pemakai harus menebak dan mendeklarasikan requires java.sql sendiri — rantai dependensi tersembunyi. Dengan requires transitive java.sql, siapa pun yang requires com.toko.erm otomatis ikut membaca java.sql. Aturan praktisnya: pakai transitive untuk module yang tipenya muncul di API publik Anda, dan requires biasa untuk kebutuhan internal.
exports dan opens untuk Reflection #
exports membuka akses waktu kompilasi dan runtime biasa: module lain boleh mengimpor class dari package itu. Ada dua variasi penting. Pertama, qualified export exports com.toko.erm.spi to com.toko.erm.plugin; — package hanya terlihat oleh module yang disebut; kita gunakan ini pada Chapter requires dan exports. Kedua, opens. Reflection — mesin di balik framework seperti Jackson, Hibernate, dan Spring — sering harus mengintip field private untuk mengisi objek. Access biasa tidak cukup; package harus dibuka khusus:
module com.toko.erm {
requires java.logging;
exports com.toko.erm.api;
opens com.toko.erm.model; // Jackson boleh memetakan class ini
// com.toko.erm.internal tetap tertutup rapat
}Perbedaan tajamnya: exports mengizinkan pemakaian terprogram (compile-time), sedangkan opens mengizinkan akses mendalam lewat setAccessible(true) di reflection. Package boleh exports tanpa opens (API terbuka tapi reflection ditolak), opens tanpa exports (hanya untuk framework), atau keduanya sekaligus.
uses dan provides #
Dua direktif terakhir mengurus layanan (service) — pola di mana module konsumen hanya mengenal interface, sementara implementasi ditemukan otomatis saat runtime. uses S menyatakan “module ini meminta implementasi layanan S ke ServiceLoader”, dan provides S with I menyatakan “module ini menyuplai implementasi I untuk layanan S”. Karena topik ini cukup kaya untuk satu chapter penuh, kita bahas tuntas beserta contoh yang berjalan pada Services dan Providers.
Membaca Deskriptor dari Dalam Program #
Class ModuleDescriptor di java.lang.module memungkinkan kita membaca isi deskriptor module mana pun saat runtime. Program berikut membongkar deskriptor java.sql milik JDK:
import java.lang.module.ModuleDescriptor;
import java.util.Comparator;
public class InfoDeskriptor {
public static void main(String[] args) {
ModuleDescriptor deskripsi = java.sql.Date.class.getModule().getDescriptor();
System.out.println("nama : " + deskripsi.name());
System.out.println("requires :");
deskripsi.requires().stream()
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
.forEach(r -> {
String tanda = r.modifiers()
.contains(ModuleDescriptor.Requires.Modifier.TRANSITIVE)
? " (transitive)" : "";
System.out.println(" " + r.name() + tanda);
});
System.out.println("exports : " + deskripsi.exports().stream()
.map(ModuleDescriptor.Exports::source)
.sorted()
.toList());
System.out.println("uses : " + deskripsi.uses());
}
}Output yang Dihasilkan:
nama : java.sql
requires :
java.base
java.logging (transitive)
java.transaction.xa (transitive)
java.xml (transitive)
exports : [java.sql, javax.sql]
uses : [java.sql.Driver]Mari kita bedah hasil cetaknya:
java.basetanpa tanda: setiap module membacajava.base— di deskriptorjava.sqlketergantungan itu ditulis eksplisit, pada module Anda sendiri JVM menambahkannya secara implisit.- Tiga module bertanda
transitive: tipe darijava.logging,java.transaction.xa, danjava.xmlmuncul di API publik JDBC, sehingga pemakaijava.sqlotomatis ikut membacanya — penerapan langsung aturanrequires transitivedi atas. exports [java.sql, javax.sql]: hanya dua package yang menjadi API; seluruh package internaljava.sql(misalnyajdk.internal.milik implementasi) tidak tercantum dan tertutup bagi kita.uses [java.sql.Driver]: JDBC mencari driver database lewatServiceLoader— kita akan melihat mekanisme lengkapnya pada Chapter Services dan Providers.- Dua
.sorted(...)pada stream:requires()danexports()sama-sama mengembalikanSetyang urutan iterasinya tidak dijamin stabil antar eksekusi JVM; hasilnya kita urutkan berdasarkan nama agar output program dapat diandalkan dari eksekusi ke eksekusi — kebiasaan penting untuk setiap program yang mencetak isi koleksi tak berurut.
Menampilkan Deskriptor dengan java –describe-module #
Launcher memiliki opsi praktis untuk melihat deskriptor tanpa menulis kode — pada module sistem maupun module kita sendiri:
java --describe-module java.sqlOutput yang Dihasilkan:
[email protected]
exports java.sql
exports javax.sql
requires java.base mandated
requires java.transaction.xa transitive
requires java.logging transitive
requires java.xml transitive
uses java.sql.DriverEnam baris tengahnya persis isi module-info.java milik java.sql di kode sumber JDK — mandated menandai requires java.base yang memang diwajibkan spesifikasi. Opsi yang sama bekerja untuk module hasil kompilasi Anda sendiri cukup dengan menyebut module path-nya.
Deskriptor kini kita pahami sebagai kontrak tertulis. Chapter berikutnya menghidupkannya: kita akan menyusun dua module sungguhan, mengompilasinya dengan --module-source-path, mengamati graf dependensinya, lalu melihat error presisi yang muncul ketika requires atau exports kurang satu baris.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
InfoDeskriptor.java
Unduh berkasimport java.lang.module.ModuleDescriptor;
import java.util.Comparator;
public class InfoDeskriptor {
public static void main(String[] args) {
ModuleDescriptor deskripsi = java.sql.Date.class.getModule().getDescriptor();
System.out.println("nama : " + deskripsi.name());
System.out.println("requires :");
deskripsi.requires().stream()
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
.forEach(r -> {
String tanda = r.modifiers()
.contains(ModuleDescriptor.Requires.Modifier.TRANSITIVE)
? " (transitive)" : "";
System.out.println(" " + r.name() + tanda);
});
System.out.println("exports : " + deskripsi.exports().stream()
.map(ModuleDescriptor.Exports::source)
.sorted()
.toList());
System.out.println("uses : " + deskripsi.uses());
}
}