Episode ini membedah jantung Spring: bean dan dependency injection. Kalian mempelajari annotation @Component, @Service, @Repository, dan @Controller, perbandingan constructor vs field vs setter injection, scope bean standar, serta lifecycle callbacks.

Di episode 2 kalian sudah melihat application context menyimpan semua bean. Sekarang kita membuka detailnya: bagaimana sebuah class menjadi bean, bagaimana Spring menyuntikkan dependency, dan bagaimana mengontrol siklus hidup serta scope-nya.
Dependency Injection adalah alasan utama orang memilih Spring. Kalian akan menulis kode yang lebih mudah diuji, lebih bersih, dan terpisah dari konfigurasi. Episode 4 ini adalah fondasi yang akan kalian pakai di seluruh sisa series.
Bean adalah object yang dibuat, dikelola, dan di-inject oleh Spring container. Kalian tidak memanggil new sendiri — Spring yang melakukannya, menyimpan instance-nya di application context, dan menyuntikkannya ke class lain yang membutuhkan.
Spring menemukan bean melalui component scan — pemindaian otomatis pada package dan sub-package dari kelas utama. Untuk menandai sebuah class sebagai bean, gunakan salah satu annotation berikut:
@Component — komponen generik.@Service — kelas dengan logika bisnis.@Repository — kelas akses data, dengan terjemahan exception database otomatis.@Controller / @RestController — kelas yang menangani request HTTP.@Service
public class ItemService {
// logika bisnis
}
@Repository
public class ItemRepository {
// akses database
}
@RestController
public class ItemController {
// endpoint HTTP
}Secara teknis @Service, @Repository, dan @Controller adalah meta-annotation dari @Component. Namun memakai annotation yang spesifik memperjelas peran setiap class dan memicu perilaku tambahan — misalnya @Repository mengubah exception database menjadi DataAccessException Spring.
Cara terbaik dan direkomendasikan Spring adalah constructor injection: dependency dideklarasikan sebagai parameter constructor final:
@Service
public class ItemService {
private final ItemRepository repository;
public ItemService(ItemRepository repository) {
this.repository = repository;
}
}Karena class hanya punya satu constructor, Spring bisa menyuntikkan dependency tanpa annotation tambahan. Field yang final memastikan dependency tidak bisa berubah setelah dibuat, sehingga test menjadi mudah — kalian cukup mengoper mock lewat constructor.
Field injection memakai @Autowired langsung di field — ringkas tapi menyembunyikan dependency dan menyulitkan testing. Setter injection memakai setter — berguna untuk dependency yang opsional. Keduanya dibolehkan, tapi untuk kode baru, konsistensi constructor injection lebih diutamakan.
Scope menentukan berapa lama dan bagaimana sebuah bean hidup:
@Service
@Scope("prototype")
public class AuditLogger {
// instance baru setiap dipakai
}Singleton adalah default yang tepat untuk bean tanpa state, seperti service dan repository. Gunakan prototype hanya untuk object yang menyimpan state per pemakaian.
Ketika bean berasal dari library pihak ketiga — misalnya RestClient atau PasswordEncoder — kalian tidak bisa menandainya dengan @Component. Solusinya: deklarasikan lewat method @Bean di dalam class @Configuration:
@Configuration
public class AppConfig {
@Bean
public RestClient restClient() {
return RestClient.builder()
.baseUrl("https://api.example.com")
.build();
}
}Method @Bean dipanggil oleh container saat aplikasi start, dan hasilnya disimpan sebagai bean dengan nama sesuai nama method. Cara ini juga berguna untuk men-setup bean dengan konfigurasi awal yang kompleks.
Bean bisa menjalankan logika saat dibuat dan saat dihancurkan. Spring menyediakan dua annotation: @PostConstruct untuk inisialisasi setelah dependency ter-inject, dan @PreDestroy untuk pembersihan sebelum container menutup bean.
@Component
public class CacheWarmer {
@PostConstruct
public void init() {
// siapkan cache saat aplikasi start
}
@PreDestroy
public void cleanup() {
// bersihkan resource saat aplikasi berhenti
}
}Annotation ini penting untuk menaruh logika inisialisasi yang butuh dependency sudah terpasang — sesuatu yang tidak bisa dilakukan di constructor.
Dua masalah yang paling sering ditemui: circular dependency, ketika bean A membutuhkan B dan B membutuhkan A sehingga container tidak bisa menyelesaikan keduanya, dan NoSuchBeanDefinitionException, ketika tidak ada bean yang cocok dengan tipe yang diminta. Solusi untuk circular dependency: refactor agar satu arah, atau gunakan @Lazy pada salah satu sisi. Untuk bean yang hilang, periksa package scan — pastikan class berada di dalam package kelas utama atau sub-package-nya. Untuk melihat daftar bean yang berhasil ter-registrasi, jalankan aplikasi dengan ./mvnw spring-boot:run lalu periksa endpoint actuator beans.
Circular dependency -> refactor ke satu arah
Bean tidak ditemukan -> periksa component scanJika kedua kasus muncul, bacalah stack trace lengkapnya — Spring biasanya memberi tahu persis bean mana yang bermasalah.
Episode 4 membekali kalian dengan pemahaman inti Spring: bean sebagai object yang dikelola container, stereotype annotation untuk menandai peran class, perbandingan tiga gaya injection dengan constructor sebagai pilihan utama, scope singleton dan prototype, custom bean dengan @Bean, serta lifecycle callbacks.
Inti yang harus dibawa pulang:
new manual.@Service, @Repository, dan @Controller memperjelas peran setiap class.@Bean di class @Configuration.@PostConstruct dan @PreDestroy mengelola inisialisasi serta pembersihan bean.Di episode 5 selanjutnya kita akan membangun web MVC dan REST API dasar — @RestController dan @RequestMapping, path variable, request param, request body, headers, hingga ResponseEntity dengan status code dan serialisasi JSON. Kalian akan mulai menghasilkan API yang benar-benar berguna.