/blog/*

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: