Filosofi convention over configuration hanya bermanfaat jika kita hafal konvensinya. Maven menetapkan satu peta direktori yang sama untuk jutaan proyek di seluruh dunia: kode produksi di src/main/java, kode pengujian di src/test/java, berkas pendukung di src/main/resources, dan seluruh hasil build menumpuk di target/. Dengan peta yang seragam, anggota tim baru dapat membuka proyek Maven mana pun dan langsung tahu di mana setiap jenis berkas berada — tanpa membaca satu baris konfigurasi. Pada chapter ini kita akan menelusuri struktur tersebut dan memahami pemetaan groupId ke struktur package yang berada di baliknya.
Peta Direktori Standar #
Program berikut membangun peta direktori standar Maven untuk proyek com.toko.gudang:aplikasi-gudang:1.0.0 — struktur yang dijamin sama di semua mesin yang menjalankan Maven:
public class PetaStruktur {
public static void main(String[] args) {
String groupId = "com.toko.gudang";
String artifactId = "aplikasi-gudang";
String version = "1.0.0";
// groupId menjadi package, titik diganti pemisah direktori
String pathPackage = groupId.replace('.', '/');
String berkasJar = artifactId + "-" + version + ".jar";
System.out.println(artifactId + "/");
System.out.println("├── pom.xml");
System.out.println("├── src/");
System.out.println("│ ├── main/");
System.out.println("│ │ ├── java/");
System.out.println("│ │ │ └── " + pathPackage + "/");
System.out.println("│ │ │ ├── AplikasiGudang.java");
System.out.println("│ │ │ └── layanan/Penyimpanan.java");
System.out.println("│ │ └── resources/");
System.out.println("│ │ └── aplikasi.properties");
System.out.println("│ └── test/");
System.out.println("│ ├── java/");
System.out.println("│ │ └── " + pathPackage + "/");
System.out.println("│ │ └── AplikasiGudangTest.java");
System.out.println("│ └── resources/");
System.out.println("│ └── contoh-data.csv");
System.out.println("└── target/");
System.out.println(" └── " + berkasJar);
System.out.println();
System.out.println("package di AplikasiGudang.java : " + groupId);
System.out.println("path di bawah src/main/java/ : " + pathPackage);
}
}Output yang Dihasilkan:
aplikasi-gudang/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/toko/gudang/
│ │ │ ├── AplikasiGudang.java
│ │ │ └── layanan/Penyimpanan.java
│ │ └── resources/
│ │ └── aplikasi.properties
│ └── test/
│ ├── java/
│ │ └── com/toko/gudang/
│ │ └── AplikasiGudangTest.java
│ └── resources/
│ └── contoh-data.csv
└── target/
└── aplikasi-gudang-1.0.0.jar
package di AplikasiGudang.java : com.toko.gudang
path di bawah src/main/java/ : com/toko/gudangTiga pemetaan penting dari output di atas:
- Baris
package di AplikasiGudang.java: berkas sumber berada di packagecom.toko.gudang— Maven mengharapkan package mengikutigroupIdagar artifact orang lain tidak bertabrakan nama package-nya dengan artifact Anda. - Baris
path di bawah src/main/java/: setiap titik pada package menjadi pemisah direktori, persis seperti aturanjavac -dpada chapter kompilasi manual; Maven hanya mengotomatiskan pencarian berkasnya. - Baris
└── aplikasi-gudang-1.0.0.jar: hasil akhir ditarget/bernamaartifactId-version.jar— nama yang bisa diprediksi dari POM, sama seperti pola berkas di repositori.
groupId Menjadi Struktur Package #
Aturan praktisnya sederhana: package dasar proyek mengikuti groupId, dan direktori berkas mengikuti package. Package com.toko.gudang.layanan berarti berkas berada di src/main/java/com/toko/gudang/layanan/. Struktur kode uji juga mengikuti package yang sama — AplikasiGudangTest di src/test/java/com/toko/gudang/ dapat mengakses member ber-modifier package-private milik class yang diuji karena keduanya se-package.
Direktori resources #
Semua yang bukan kode Java tetapi perlu hadir di classpath diletakkan di src/main/resources: berkas .properties konfigurasi, .xml mapping, .json data contoh, hingga template email. Maven menyalin seluruh isi direktori tersebut apa adanya ke target/classes, sehingga saat runtime struktur menjadi menyatu — bytecode dan berkas resource berdampingan, dan getClass().getResourceAsStream("/aplikasi.properties") menemukannya di root classpath.
Untuk kode pengujian, src/test/resources berperan sama: berkas contoh-data.csv di atas akan tersalin ke target/test-classes dan hanya terlihat saat pengujian berjalan — tidak ikut terbawa ke JAR produksi.
Direktori target #
Seluruh hasil kerja Maven ditulis ke target/: target/classes berisi bytecode hasil kompilasi utama, target/test-classes berisi bytecode pengujian, target/surefire-reports berisi laporan pengujian, dan berkas JAR final berada tepat di akar target/. Direktori ini sepenuhnya milik Maven — tidak ada satu berkas pun di dalamnya yang perlu diedit manual atau disimpan di sistem kontrol versi.
mvn cleanPerintah mvn clean menghapus seluruh target/ sebagai awal build yang segar; Maven akan membuat ulang isinya dari src/ dan POM. Karena itu pula, berkas .gitignore proyek Maven standar memuat baris target/.
Struktur Pengujian yang Sejajar #
Direktori src/test meniru src/main secara penuh: ada java/ untuk kode uji dan resources/ untuk data uji. Maven memperlakukan keduanya sebagai source root terpisah dengan classpath berbeda — kode produksi tidak dapat mengakses class pengujian, tetapi kode pengujian dapat mengakses seluruh class produksi. Plugin pengujian bawaan (maven-surefire-plugin) mencari class uji berdasarkan pola nama seperti *Test dan Test*, sehingga AplikasiGudangTest.java di peta di atas otomatis dikenali tanpa konfigurasi.
Struktur direktori menuntaskan sisi “di mana” dari konvensi Maven. Pertanyaan berikutnya adalah “dari mana” — bagaimana Maven menghadirkan library eksternal seperti commons-lang3 atau JUnit ke dalam classpath proyek kita. Jawabannya ada pada deklarasi dependency, topik chapter berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PetaStruktur.java
Unduh berkaspublic class PetaStruktur {
public static void main(String[] args) {
String groupId = "com.toko.gudang";
String artifactId = "aplikasi-gudang";
String version = "1.0.0";
// groupId menjadi package, titik diganti pemisah direktori
String pathPackage = groupId.replace('.', '/');
String berkasJar = artifactId + "-" + version + ".jar";
System.out.println(artifactId + "/");
System.out.println("├── pom.xml");
System.out.println("├── src/");
System.out.println("│ ├── main/");
System.out.println("│ │ ├── java/");
System.out.println("│ │ │ └── " + pathPackage + "/");
System.out.println("│ │ │ ├── AplikasiGudang.java");
System.out.println("│ │ │ └── layanan/Penyimpanan.java");
System.out.println("│ │ └── resources/");
System.out.println("│ │ └── aplikasi.properties");
System.out.println("│ └── test/");
System.out.println("│ ├── java/");
System.out.println("│ │ └── " + pathPackage + "/");
System.out.println("│ │ └── AplikasiGudangTest.java");
System.out.println("│ └── resources/");
System.out.println("│ └── contoh-data.csv");
System.out.println("└── target/");
System.out.println(" └── " + berkasJar);
System.out.println();
System.out.println("package di AplikasiGudang.java : " + groupId);
System.out.println("path di bawah src/main/java/ : " + pathPackage);
}
}