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:

text
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/gudang

Tiga pemetaan penting dari output di atas:

  1. Baris package di AplikasiGudang.java: berkas sumber berada di package com.toko.gudang — Maven mengharapkan package mengikuti groupId agar artifact orang lain tidak bertabrakan nama package-nya dengan artifact Anda.
  2. Baris path di bawah src/main/java/: setiap titik pada package menjadi pemisah direktori, persis seperti aturan javac -d pada chapter kompilasi manual; Maven hanya mengotomatiskan pencarian berkasnya.
  3. Baris └── aplikasi-gudang-1.0.0.jar: hasil akhir di target/ bernama artifactId-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.

bash
mvn clean

Perintah 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 berkas
java
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);
    }
}