Spring Framework klasik menyambungkan dependensi dengan indah, tetapi membangun aplikasi web utuh dengannya masih menuntut konfigurasi panjang: mendefinisikan DispatcherServlet di web.xml, mengatur view resolver, menyiapkan DataSource dan transaction manager satu per satu. Tahun 2014, Spring Boot hadir untuk memangkas pekerjaan itu dengan tiga gagasan: auto-configuration, starter, dan embedded server. Pada Chapter ini kita bedah ketiganya serta prinsip convention over configuration yang menjadikan aplikasi Spring semakin cepat dimulai.
Auto-Configuration Berdasarkan Kondisi #
Auto-configuration adalah kode konfigurasi pabrikan yang aktif hanya bila kondisinya terpenuhi. Anotasi @EnableAutoConfiguration (termasuk otomatis dalam @SpringBootApplication) memicu Spring Boot membaca ratusan kelas konfigurasi kandidat dari pustaka di classpath. Setiap kandidat dipasangi anotasi kondisional — @ConditionalOnClass memeriksa keberadaan kelas tertentu, @ConditionalOnMissingBean mundur bila kita sudah menyediakan bean sendiri, @ConditionalOnProperty memeriksa nilai properti. Hasil akhirnya: konfigurasi yang menyesuaikan diri dengan pustaka yang kita pakai.
Berikut potongan kelas auto-configuration nyata yang ditampilkan apa adanya untuk Spring Boot 3.4/3.5:
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnProperty("spring.datasource.url")
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}Mekanisme mundurnya penting dicamkan: anotasi @ConditionalOnMissingBean berarti konfigurasi pabrikan selalu tunduk pada bean yang kita definisikan sendiri. Kita bisa menimpa hampir semua perilaku bawaan tanpa melawan framework — cukup mendeklarasikan bean dengan tipe yang sama.
Starter sebagai Satuan Kebutuhan #
Masalah klasik dependency management adalah dependency hell: satu fitur menuntut belasan pustaka dengan versi yang harus saling cocok. Starter menyelesaikannya dengan mengemas satu kebutuhan fungsional menjadi satu koordinat Maven — spring-boot-starter-web membawa Spring MVC, Jackson, dan Tomcat; spring-boot-starter-data-jpa membawa Hibernate dan Spring Data; spring-boot-starter-test membawa JUnit 5, AssertJ, dan Mockito. Versi seluruh pustaka di dalamnya sudah diuji kompatibel oleh tim Spring Boot, dan nomor versi starter cukup satu: versi Boot.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>Perhatikan bahwa dependency starter di atas tidak memuat elemen <version> — parent POM spring-boot-starter-parent mengelola versi lewat dependency management. Pola parent POM ini juga menyeting encoding UTF-8, plugin Java, dan konfigurasi filter resource secara baku.
Mensimulasikan Tabel Keputusan #
Dengan Java murni, kita bisa merekonstruksi inti pengambilan keputusan auto-configuration: tabel kandidat dengan syarat kelas dan properti, dievaluasi satu per satu:
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TabelAutoconfig {
// Satu kandidat auto-configuration dengan syarat @ConditionalOnClass
// dan (opsional) syarat @ConditionalOnProperty.
record Kandidat(String nama, String kelasWajib, String propertiWajib, String efek) { }
private static final List<Kandidat> KANDIDAT = List.of(
new Kandidat("DataSourceAutoConfiguration",
"javax.sql.DataSource", "spring.datasource.url",
"pool koneksi HikariCP dibuat"),
new Kandidat("WebMvcAutoConfiguration",
"org.springframework.web.servlet.DispatcherServlet", null,
"infrastruktur MVC + Jackson terpasang"),
new Kandidat("JpaRepositoriesAutoConfiguration",
"jakarta.persistence.EntityManager", null,
"infrastruktur Spring Data JPA aktif"),
new Kandidat("RedisAutoConfiguration",
"org.springframework.data.redis.RedisConnection", null,
"cache Redis"),
new Kandidat("TaskSchedulingAutoConfiguration",
"org.springframework.scheduling.TaskScheduler", "spring.task.scheduling.pool.size",
"executor @Scheduled dikonfigurasi"));
public static void main(String[] args) {
// Simulasi classpath hasil starter web + data-jpa + scheduling.
Set<String> kelasDiClasspath = Set.of(
"javax.sql.DataSource",
"org.springframework.web.servlet.DispatcherServlet",
"jakarta.persistence.EntityManager",
"org.springframework.scheduling.TaskScheduler");
// Simulasi application.properties yang ditulis pengembang.
Map<String, String> properti = new LinkedHashMap<>();
properti.put("spring.datasource.url", "jdbc:h2:mem:demo");
properti.put("spring.task.scheduling.pool.size", "4");
System.out.println("Kelas kunci di classpath : " + kelasDiClasspath.size());
System.out.println("Properti pengembang : " + properti.keySet());
System.out.println();
int aktif = 0;
for (Kandidat k : KANDIDAT) {
boolean kelasOk = kelasDiClasspath.contains(k.kelasWajib());
boolean propOk = k.propertiWajib() == null
|| properti.containsKey(k.propertiWajib());
boolean cocok = kelasOk && propOk;
if (cocok) {
aktif++;
}
System.out.println((cocok ? "MATCH " : "LEWAT ") + k.nama());
System.out.println(" syarat kelas : " + k.kelasWajib()
+ " -> " + (kelasOk ? "ada" : "tidak ada"));
if (k.propertiWajib() != null) {
System.out.println(" syarat properti : " + k.propertiWajib()
+ " -> " + (propOk ? "terisi" : "kosong"));
}
System.out.println(" hasil : "
+ (cocok ? k.efek() : "konfigurasi dilewati"));
System.out.println();
}
System.out.println("Total aktif: " + aktif + " dari " + KANDIDAT.size() + " kandidat");
}
}Output yang Dihasilkan:
Kelas kunci di classpath : 4
Properti pengembang : [spring.datasource.url, spring.task.scheduling.pool.size]
MATCH DataSourceAutoConfiguration
syarat kelas : javax.sql.DataSource -> ada
syarat properti : spring.datasource.url -> terisi
hasil : pool koneksi HikariCP dibuat
MATCH WebMvcAutoConfiguration
syarat kelas : org.springframework.web.servlet.DispatcherServlet -> ada
hasil : infrastruktur MVC + Jackson terpasang
MATCH JpaRepositoriesAutoConfiguration
syarat kelas : jakarta.persistence.EntityManager -> ada
hasil : infrastruktur Spring Data JPA aktif
LEWAT RedisAutoConfiguration
syarat kelas : org.springframework.data.redis.RedisConnection -> tidak ada
hasil : konfigurasi dilewati
MATCH TaskSchedulingAutoConfiguration
syarat kelas : org.springframework.scheduling.TaskScheduler -> ada
syarat properti : spring.task.scheduling.pool.size -> terisi
hasil : executor @Scheduled dikonfigurasi
Total aktif: 4 dari 5 kandidatMari kita bedah simulasi tersebut:
Set<String> kelasDiClasspath: himpunan ini memainkan peran classpath nyata hasil starterweb+data-jpa+scheduling. Kandidat Redis tidak menemukan kelas kuncinya, sehingga barisnya berakhirLEWAT— persis perilaku@ConditionalOnClassyang memeriksaRedisConnection.Map<String, String> properti: kandidatDataSourceAutoConfigurationdanTaskSchedulingAutoConfigurationmencantumkan syarat properti; kandidat yang propertinya kosong dianggap tidak memenuhi syarat, menirui@ConditionalOnProperty.- Loop evaluasi: setiap kandidat menghasilkan baris
MATCHatauLEWATbeserta alasan dan efeknya. Angka akhirTotal aktif: 4 dari 5 kandidatmenunjukkan bahwa auto-configuration bukan mengaktifkan semuanya, melainkan memilih berdasarkan bukti.
Embedded Server dan Fat Jar #
Konvensi ketiga Spring Boot: aplikasi membawa server sendiri. spring-boot-starter-web menyertakan Tomcat tertanam yang berjalan di dalam JVM aplikasi — tidak ada lagi instalasi application server terpisah. Proses build menghasilkan fat jar: satu berkas JAR berisi kelas kita, seluruh pustaka dependensi, dan server tertanam, yang dijalankan cukup dengan java -jar aplikasi.jar. Penyiapan deployment menjadi urusan menyalin satu berkas, cocok untuk container image dan platform cloud modern.
Konvensi bawaannya pun menegaskan filosofi convention over configuration: kelas aplikasi di src/main/java, konfigurasi di src/main/resources/application.properties, port 8080, dan context path /. Semua bisa diubah lewat property — tetapi kita baru perlu mengubahnya saat kebutuhan sungguhan muncul.
Referensi #
- Spring Boot Reference Documentation
- Spring Boot Reference: Using the SpringApplication Class
- Spring Guides: Building an Application with Spring Boot
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
TabelAutoconfig.java
Unduh berkasimport java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TabelAutoconfig {
// Satu kandidat auto-configuration dengan syarat @ConditionalOnClass
// dan (opsional) syarat @ConditionalOnProperty.
record Kandidat(String nama, String kelasWajib, String propertiWajib, String efek) { }
private static final List<Kandidat> KANDIDAT = List.of(
new Kandidat("DataSourceAutoConfiguration",
"javax.sql.DataSource", "spring.datasource.url",
"pool koneksi HikariCP dibuat"),
new Kandidat("WebMvcAutoConfiguration",
"org.springframework.web.servlet.DispatcherServlet", null,
"infrastruktur MVC + Jackson terpasang"),
new Kandidat("JpaRepositoriesAutoConfiguration",
"jakarta.persistence.EntityManager", null,
"infrastruktur Spring Data JPA aktif"),
new Kandidat("RedisAutoConfiguration",
"org.springframework.data.redis.RedisConnection", null,
"cache Redis"),
new Kandidat("TaskSchedulingAutoConfiguration",
"org.springframework.scheduling.TaskScheduler", "spring.task.scheduling.pool.size",
"executor @Scheduled dikonfigurasi"));
public static void main(String[] args) {
// Simulasi classpath hasil starter web + data-jpa + scheduling.
Set<String> kelasDiClasspath = Set.of(
"javax.sql.DataSource",
"org.springframework.web.servlet.DispatcherServlet",
"jakarta.persistence.EntityManager",
"org.springframework.scheduling.TaskScheduler");
// Simulasi application.properties yang ditulis pengembang.
Map<String, String> properti = new LinkedHashMap<>();
properti.put("spring.datasource.url", "jdbc:h2:mem:demo");
properti.put("spring.task.scheduling.pool.size", "4");
System.out.println("Kelas kunci di classpath : " + kelasDiClasspath.size());
System.out.println("Properti pengembang : " + properti.keySet());
System.out.println();
int aktif = 0;
for (Kandidat k : KANDIDAT) {
boolean kelasOk = kelasDiClasspath.contains(k.kelasWajib());
boolean propOk = k.propertiWajib() == null
|| properti.containsKey(k.propertiWajib());
boolean cocok = kelasOk && propOk;
if (cocok) {
aktif++;
}
System.out.println((cocok ? "MATCH " : "LEWAT ") + k.nama());
System.out.println(" syarat kelas : " + k.kelasWajib()
+ " -> " + (kelasOk ? "ada" : "tidak ada"));
if (k.propertiWajib() != null) {
System.out.println(" syarat properti : " + k.propertiWajib()
+ " -> " + (propOk ? "terisi" : "kosong"));
}
System.out.println(" hasil : "
+ (cocok ? k.efek() : "konfigurasi dilewati"));
System.out.println();
}
System.out.println("Total aktif: " + aktif + " dari " + KANDIDAT.size() + " kandidat");
}
}