Spring Dependency Injection
Dependency injection (DI) merupakan aspek fundamental dari framework Spring dimana tanggung jawab proses pembuatan object dan distribusi dependency sepenuhnya diambil alih oleh Spring. DI merupakan salah satu implementasi dari prinsip Inversion of Control (IoC) yang tujuan utamanya adalah untuk menghasilkan loosely coupled antar class.
DI Manual
Dalam pemrograman orientasi object, kita akan sering sekali menemukan kasus dimana suatu class membutuhkan object dari class lain agar bisa berjalan. Berikut contohnya:
public class Car {
private Engine engine;
public void start(){
engine.turnOn();
}
}
Class Car
membutuhkan object dari class Engine
agar bisa menjalankan method start()
. Tanpa prinsip DI kita bisa saja membuat object engine
secara langsung didalam class Car
, tapi cara ini kurang efisien. Bisa saja class lain juga membutuhkan object engine
, jika object tersebut dibuat dibanyak tempat jika terjadi perubahan maka harus dirubah satu persatu.
Dengan menggunakan prinsip DI, class Car
tidak lagi mengurusi proses inisialisasi/pembuatan object engine
. Dia cukup tahu beres dan tinggal gunakan saja. Lalu siapa yang akan mengurus proses inisialiasi tersebut? Bisa siapa aja, bisa class yang akan memanggil class 'Car', atau siapa saja. Yang harus dilakukan oleh class Car
adalah menyediakan jalan masuk (inject) untuk object engine
. Ada 2 pilihan, melalui method (setter-injection) atau dengan menambahkan parameter di constructor (constructor-injection).
public class Car {
private Engine engine;
public Car(Engine engine){ // constructor-injection
this.engine = engine;
}
public void setEngine(Engine engine){ // setter-injection
this.engine = engine;
}
public void start(){
engine.turnOn();
}
}
DI dengan Spring
Dengan Spring, baik itu proses pembuatan object maupun proses injection-nya sepenuhnya dihandle. Kita hanya perlu mendefinisikan saja, bisa melalui XML atau dengan annotation. Dalam Spring, object-object yang akan dihandle disebut bean.
<bean id="engine" class="com.hiwijaya.ioc.domain.Engine"/>
<!-- car.setEngine(engine); -->
<bean id="car" class="com.hiwijaya.ioc.domain.Car">
<property name="engine" ref="combustionEngine"/>
</bean>
<!-- OR Car car = new Car(engine); -->
<bean id="car" class="com.hiwijaya.ioc.domain.Car">
<property name="engine" ref="combustionEngine"/>
</bean>
Kita juga bisa mendefinisikan bean menggunakan annotation pada suatu class konfigurasi yang ditandai dengan annotation @configuration
.
@Configuration
public class BasicConfig {
@Bean
public Car car(Engine engine) {
return new Car(engine);
}
@Bean
public Engine engine(){
return new Engine();
}
}
Autowired
Spring framework memiliki fitur component-scan dimana Spring akan melakukan proses scan pada isi package yang sudah ditentukan untuk menemukan class yang memiliki annotation @Component
. Class yang sudah ditandai dengan annotation @Component
artinya class tersebut proses inisiasinya sepenuhnya dihandle oleh Spring. Berikut contoh konfigurasi menggunakan XML:
...
<context:component-scan base-package="com.hiwijaya.ioc.domain" />
...
Atau bisa juga dengan menggunakan annotation @ComponentScan
pada class konfigurasi:
@Configuration
@ComponentScan("com.hiwijaya.ioc.domain")
public class AutomatedConfig {
}
Spring juga akan mencari dependency dari class tersebut dan langsung melakukan injection. Disini proses injeksi tidak perlu lagi melalui setter maupun constructor, cukup tandai suatu field/properti dengan annotation @Autowired
.
@Component
public class Car {
@Autowired // field injection
private Engine engine;
@Autowired // constructor injection
public Car(Engine engine) {
this.engine = engine;
}
@Autowired // setter injection
public void setEngine(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
@Value
Karena proses inisialisasi dihandle oleh Spring, bagaimana jika ada kondisi suatu class memiliki parameter constructor yang harus diisi dan tidak ada default contstructor. Bagaimana Spring mengisi parameter tersebut? Diisi dengan value apa? Dikondisi seperti ini Spring terpaksa akan melempar exception NoSuchBeanDefinitionException
. Untuk mencegah hal ini terjadi, kita bisa mendefinisikan value dari parameter tersebut dengan annotation @Value
.
@Component
public class Camshaft {
private String type;
public Camshaft(@Value("double")String type) {
this.type = type;
}
...
}
@Qualifier dan @Primary
Terdapat juga kasus dimana ketika Spring ingin melakukan injeksi dependency ada lebih dari satu class yang identik. Sebagai contoh diatas terdapat suatu class Car
yang memiliki dependency object dari class Engine
, bagaimana jika Engine tersebut merupakan Interface dan terdapat 2 buah class implementasi:
@Component()
public class CombustionEngine implements Engine {
...
}
@Component()
public class ELectricEngine implements Engine {
...
}
Pertanyaannya adalah class implementasi mana yang akan di injeksi oleh Spring di class Car
? Apakah class CombustionEngine
atau class ElectricEngine
? Spring tidak akan memilih salah satu, melainkan akan melempar exception NoUniqueBeanDefinitionException
. Untuk memberi tahu Spring class implementasi mana yang ingin kita gunakan, terdapat 2 cara yaitu dengan annotation @Qualifier
dan @Primary
.
Dengan @Qualifier
kita bisa mendefinisikan secara spesifik class mana yang ingin dipilih melalui penamaan pada annotation @Component
. Berikut contohnya:
@Component("electric")
public class ElectricEngine implements Engine {
...
}
@Component
public class Car {
@Autowired
@Qualifier("electric")
private Engine engine;
...
}
Atau kita juga bisa menentukan default dari class-class implementasi Interface Engine
dengan menggunakan annotation @Primary
. Artinya jika tidak ada annotation @Qualifier
secara spesifik, maka Spring akan menggunakan class yang ditandai dengan annotation @Primary
.
@Component()
@Primary
public class CombustionEngine implements Engine {
...
}
Application Context
Pada konsep Inversion of Control ada istilah yang dikenal dengan container. Di setiap framework yang mengimplementasikan konsep IoC, container inilah yang bertanggung jawab untuk menginisialisasi, mengkonfigurasi, menyusun, dan mengatur object-object atau beans. Pada Spring container nya adalah interface ApplicationContext
.
Ada beberapa implementasi dari interface ApplicationContext
yang biasanya saya gunakan, yaitu ClassPathXmlApplicationContext
untuk konfigurasi xml-based dan AnnotationConfigApplicationContext
untuk konfigurasi dengan annotation. Berikut contohnya:
public class XmlApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
Car car = context.getBean("car", Car.class);
car.start();
}
}
public class AutomatedApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AutomatedConfig.class);
Car car = context.getBean(Car.class);
car.start();
}
}
Semua kode program lengkap dari contoh diatas dapat diakses disini.
ref: