Membangun proyek Spring Boot dari nol secara manual menuntut keputusan yang panjang: parent POM, starter mana, versi berapa, struktur direktori seperti apa, dan berkas uji apa yang wajib ada. Spring Initializr memangkas seluruh keputusan itu menjadi satu form: pilih build tool, versi Boot, koordinat proyek, dan daftar dependensi — lalu situs itu menghasilkan kerangka proyek lengkap yang bisa diunduh sebagai ZIP. Pada Chapter ini kita telusuri form Initializr, bedah struktur hasil unduhannya, dan pahami berkas-berkas penting di dalamnya.

Form start.spring.io #

Alamat resminya adalah start.spring.io . Formulirnya meminta keputusan-keputusan berikut:

BidangPilihan umumKeterangan
ProjectMaven / Gradlebuild tool penjaga dependensi
LanguageJava / Kotlin / Groovybahasa sumber
Spring Boot3.5.xversi stabil Boot terpilih
PackagingJarfat jar untuk server tertanam
Java21baseline LTS yang didukung
Dependenciesstarter pilihanditambahkan lewat pencarian

Bagian Dependencies adalah inti pengalaman Initializr. Kolom pencarian menerima nama teknologi (“web”, “jpa”, “security”) lalu menawarkan starter yang cocok. Pilihan kita di bidang-bidang lain menyesuaikan tawaran: misalnya versi Boot yang lebih tua membatasi pilihan versi Java. Tim pengembang internal perusahaan juga bisa meng-host Initializr sendiri dengan katalog starter khusus, karena mesinnya tersedia sebagai pustaka dan layanan.

Struktur Proyek Hasil Unduhan #

ZIP hasil unduhan memuat proyek Maven siap ekspor. Berikut struktur tipikalnya untuk proyek layanan dengan starter web — ditampilkan apa adanya:

text
layanan/
|-- .gitattributes
|-- .gitignore
|-- mvnw
|-- mvnw.cmd
|-- pom.xml
`-- src/
    |-- main/
    |   |-- java/
    |   |   `-- id/example/layanan/
    |   |       `-- LayananApplication.java
    |   `-- resources/
    |       `-- application.properties
    `-- test/
        `-- java/
            `-- id/example/layanan/
                `-- LayananApplicationTests.java

Berkas mvnw adalah Maven wrapper: skrip yang mengunduh versi Maven yang tepat sehingga setiap anggota tim dan pipeline CI memakai Maven yang sama persis tanpa instalasi manual. Berkas .gitignore bawaannya sudah menyaring hasil build target/.

Untuk melihat pentingnya setiap berkas, kita bangun ulang struktur itu secara programatik dan periksa kelengkapannya:

import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class KerangkaProyek {

    // Struktur direktori sederhana untuk pohon berkas.
    static class Node {
        final TreeMap<String, Node> anak = new TreeMap<>();
        boolean berkas = false;
    }

    private static final List<String> BERKAS = List.of(
            ".gitattributes",
            ".gitignore",
            "mvnw",
            "mvnw.cmd",
            "pom.xml",
            "src/main/java/id/example/layanan/LayananApplication.java",
            "src/main/resources/application.properties",
            "src/main/resources/static/index.html",
            "src/main/resources/templates/beranda.html",
            "src/test/java/id/example/layanan/LayananApplicationTests.java");

    private static final List<String> WAJIB = List.of(
            "pom.xml",
            "mvnw",
            "src/main/java/id/example/layanan/LayananApplication.java",
            "src/main/resources/application.properties",
            "src/test/java/id/example/layanan/LayananApplicationTests.java");

    public static void main(String[] args) {
        Node akar = new Node();
        for (String berkas : BERKAS) {
            Node sekarang = akar;
            String[] bagian = berkas.split("/");
            for (int i = 0; i < bagian.length; i++) {
                sekarang = sekarang.anak.computeIfAbsent(bagian[i], k -> new Node());
                if (i == bagian.length - 1) {
                    sekarang.berkas = true;
                }
            }
        }

        System.out.println("layanan/");
        cetakPohon(akar, "");

        System.out.println();
        System.out.println("== pemeriksaan struktur hasil Initializr ==");
        int kurang = 0;
        for (String wajib : WAJIB) {
            boolean ada = BERKAS.contains(wajib);
            System.out.println((ada ? "[OK]     " : "[KURANG] ") + wajib);
            if (!ada) {
                kurang++;
            }
        }
        System.out.println();
        System.out.println(kurang == 0
                ? "Proyek lengkap: siap diimpor ke IDE dan dijalankan."
                : "Terdapat " + kurang + " berkas wajib yang hilang.");
    }

    private static void cetakPohon(Node node, String prefiks) {
        Map<String, Node> anak = node.anak;
        int i = 0;
        for (Map.Entry<String, Node> e : anak.entrySet()) {
            String cabang = (i == anak.size() - 1) ? "`-- " : "|-- ";
            String tipe = e.getValue().berkas ? "" : "/";
            System.out.println(prefiks + cabang + e.getKey() + tipe);
            cetakPohon(e.getValue(), prefiks + (i == anak.size() - 1 ? "    " : "|   "));
            i++;
        }
    }
}

Output yang Dihasilkan:

text
layanan/
|-- .gitattributes
|-- .gitignore
|-- mvnw
|-- mvnw.cmd
|-- pom.xml
`-- src/
    |-- main/
    |   |-- java/
    |   |   `-- id/
    |   |       `-- example/
    |   |           `-- layanan/
    |   |               `-- LayananApplication.java
    |   `-- resources/
    |       |-- application.properties
    |       |-- static/
    |       |   `-- index.html
    |       `-- templates/
    |           `-- beranda.html
    `-- test/
        `-- java/
            `-- id/
                `-- example/
                    `-- layanan/
                        `-- LayananApplicationTests.java

== pemeriksaan struktur hasil Initializr ==
[OK]     pom.xml
[OK]     mvnw
[OK]     src/main/java/id/example/layanan/LayananApplication.java
[OK]     src/main/resources/application.properties
[OK]     src/test/java/id/example/layanan/LayananApplicationTests.java

Proyek lengkap: siap diimpor ke IDE dan dijalankan.

Mari kita bedah program di atas:

  1. class Node dengan TreeMap<String, Node> anak: struktur pohon direktori. TreeMap menjaga anak tersortir menurut abjad sehingga pohon yang tercetak selalu sama urutannya — application.properties selalu di atas static/.
  2. Loop BERKAS: setiap path dipecah dengan split("/") lalu node dibuat bertingkat dengan computeIfAbsent; node paling bawah ditandai berkas = true untuk membedakan berkas dari direktori.
  3. cetakPohon(node, prefiks): method rekursif merender cabang dengan garis |-- dan backtick `-- untuk anak terakhir, memakai prefiks spasi yang menjorok sesuai kedalaman — teknik yang sama dipakai perintah tree.
  4. Pemeriksaan WAJIB: daftar berkas wajib Initializr diperiksa satu per satu terhadap struktur; hasil [OK] untuk semua menandakan proyek lengkap. Dalam CI, pemeriksaan serupa sering dipakai sebagai sanity check artefak.

Tiga Berkas Kunci #

pom.xml adalah pusat kendali dependensi — ditampilkan apa adanya untuk Spring Boot 3.5:

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/>
    </parent>

    <groupId>id.example</groupId>
    <artifactId>layanan</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>layanan</name>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

LayananApplication.java adalah titik masuk aplikasi — ditampilkan apa adanya:

java
@SpringBootApplication
public class LayananApplication {

    public static void main(String[] args) {
        SpringApplication.run(LayananApplication.class, args);
    }
}

Satu anotasi @SpringBootApplication sekaligus memuat tiga anotasi: @SpringBootConfiguration menandai kelas konfigurasi, @EnableAutoConfiguration mengaktifkan auto-configuration, dan @ComponentScan memindai paket kelas ini ke bawah untuk menemukan komponen. Karena itu kelas aplikasi dianjurkan berada di paket akar — seluruh kode lain hidup di bawahnya agar ikut terpindai.

LayananApplicationTests.java sudah berisi satu uji konteks: @SpringBootTest memastikan context aplikasi bisa naik — jaring pengaman pertama bahwa seluruh bean tersambung. Setelah kerangka berdiri, langkah berikutnya adalah mengisinya dengan konfigurasi aplikasi lewat application.properties, bahasan Chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

KerangkaProyek.java

Unduh berkas
java
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class KerangkaProyek {

    // Struktur direktori sederhana untuk pohon berkas.
    static class Node {
        final TreeMap<String, Node> anak = new TreeMap<>();
        boolean berkas = false;
    }

    private static final List<String> BERKAS = List.of(
            ".gitattributes",
            ".gitignore",
            "mvnw",
            "mvnw.cmd",
            "pom.xml",
            "src/main/java/id/example/layanan/LayananApplication.java",
            "src/main/resources/application.properties",
            "src/main/resources/static/index.html",
            "src/main/resources/templates/beranda.html",
            "src/test/java/id/example/layanan/LayananApplicationTests.java");

    private static final List<String> WAJIB = List.of(
            "pom.xml",
            "mvnw",
            "src/main/java/id/example/layanan/LayananApplication.java",
            "src/main/resources/application.properties",
            "src/test/java/id/example/layanan/LayananApplicationTests.java");

    public static void main(String[] args) {
        Node akar = new Node();
        for (String berkas : BERKAS) {
            Node sekarang = akar;
            String[] bagian = berkas.split("/");
            for (int i = 0; i < bagian.length; i++) {
                sekarang = sekarang.anak.computeIfAbsent(bagian[i], k -> new Node());
                if (i == bagian.length - 1) {
                    sekarang.berkas = true;
                }
            }
        }

        System.out.println("layanan/");
        cetakPohon(akar, "");

        System.out.println();
        System.out.println("== pemeriksaan struktur hasil Initializr ==");
        int kurang = 0;
        for (String wajib : WAJIB) {
            boolean ada = BERKAS.contains(wajib);
            System.out.println((ada ? "[OK]     " : "[KURANG] ") + wajib);
            if (!ada) {
                kurang++;
            }
        }
        System.out.println();
        System.out.println(kurang == 0
                ? "Proyek lengkap: siap diimpor ke IDE dan dijalankan."
                : "Terdapat " + kurang + " berkas wajib yang hilang.");
    }

    private static void cetakPohon(Node node, String prefiks) {
        Map<String, Node> anak = node.anak;
        int i = 0;
        for (Map.Entry<String, Node> e : anak.entrySet()) {
            String cabang = (i == anak.size() - 1) ? "`-- " : "|-- ";
            String tipe = e.getValue().berkas ? "" : "/";
            System.out.println(prefiks + cabang + e.getKey() + tipe);
            cetakPohon(e.getValue(), prefiks + (i == anak.size() - 1 ? "    " : "|   "));
            i++;
        }
    }
}