Dagger2란?

KIMGEUNTAE·2022년 12월 15일
1
post-thumbnail

Dagger2

Dagger2는 객체 간의 종속성을 관리하는 코드를 자동으로 생성 하는 도구

의존성 주입에 필요한 요소들

  • Component :
    클래스의 인스턴스를 모아놓는 역할 각 인스턴스들은 Module 단위로 선언하여 제공

  • Module :
    @Provider 하기위한 메소드를 Module 클래스의 인스턴스를 모아 놓는 역활

  • Provider :
    제공(provider) 하고 싶은 객체의 인스턴스 메소드에 붙이는 것으로 클래스에 무엇을 제공@Provider하고 싶은 지를 알리는 것

Dagger2에 사용되는 개념

* Constructor
* Provision
* Member Injection
* Named Injection
* Scope
* SubComponent
* Binds
* BindsInstance

@Inject Constructor

  • 필드, 생성자(주생성자), 메서드에 붙여 Component로 부터 의존성 객체를 주입 요청하는 annotation

  • @Inject로 의존성 주입을 요청하면 연결된 ComponentModule로부터 객체를 생성하여 넘겨주고
    Component@Inject annotation을 의존성 주입할 멤버변수와 생성자에 달아줌으로 DI 대상을 확인할 수 있음

  • 객체(인스턴스)의 생성이 클래스에서 이루어지지 않고, Component가 생성해줌

  • 기본적으로 Dagger는 요청된 Type(자료형)에 맞게 ComponentModule로부터 객체를 생성하여 주입 함

그러나

  • InterfaceActivityFragment등의 Android 환경 자체 Lifecycle을 가진 객체에는 사용할 수 없음

Car.kt

class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {

      companion object {
          private const val TAG = "Dagger Car"
      }

      fun drive() {
          Log.d(TAG, "나는 드라이버")
      }
}

CarComponent.kt

@Component
interface CarComponent {
    fun getCar() : Car
}

Engine.kt

class Engine @Inject constructor()

Wheels.kt

class Wheels @Inject constructor()

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //Interface 구현하는 Dagger(xxx)Component 객체를 반환하는 create() 메서드를 제공
        //이를 이용해 만들어진 DaggerCarComponent 인스턴스로 Dagger를 통해 객체가 생성되고 car 구성요소가 생성
        val carComponent : CarComponent = DaggerCarComponent.create()

        val car: Car = carComponent.getCar()

        car.drive()
    }
}

Provision

  • Provider에 의해 만들어진 인스턴스나, 프로퍼티가 주입된 인스턴스를 반환하는 메소드 이며 생성된 Component 클래스에서 Provision 메서드를 통해 객체를 얻을 수 있음

CarComponent.kt

@Component(modules = [WheelsModule::class])
interface CarComponent {
    fun getCar() : Car
}

Wheels.kt

class Wheels @Inject constructor(val rims: Rims, val tires: Tires)

Rims.kt

class Rims {}

Tires.kt

class Tires {
    fun inflateTires() {
        Log.d(TAG, "Tires are inflated")
    }
}

WheelsModule.kt

@Module
class WheelsModule {

    @Module
    companion object {

        @JvmStatic
        @Provides
        fun provideRims() = Rims()

        @JvmStatic
        @Provides
        fun provideTires(): Tires {
            val tires = Tires()
            tires.inflateTires()
            return tires
        }

        @JvmStatic
        @Provides
        fun provideWheels(rims: Rims, tires: Tires) = Wheels(rims, tires)
    }
}

Member Injection

  • Member Injection에는 인스턴스를 만든 다음 Provider에 의해 제공되는 인스턴스 이며 의존성을 주입시킬 객체를 메서드의 파라미터로 넘기는 방식

  • 두가지 방식으로 주입 되며 필드주입, 메서드 주입 방식


  • Field Injection
    -> 필드가 정의된 인스턴스를 만들고 필드에 값을 주입하는 방식

CarComponent.kt

@Component
interface CarComponent {
    fun getCar() : Car

    fun inject(mainActivity: MainActivity)
}

CarComponent.kt


class MainActivity : AppCompatActivity() {

    //멤버 인젝션(Member Injection)메서드
    // -> 인스턴스를 만든 다음 Provider에 의해 제공되는 인스턴스를 주입시킴

    //필드 주입(Field Injection)
    // -> 필드가 정의된 인스턴스를 필드 자체에 값을 주입하는 방식
    @Inject
    lateinit var car : Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val carComponent : CarComponent = DaggerCarComponent.create()

        carComponent.inject(this)

        car.drive()
    }

  • Mehtod Injection
    -> 메서드에 파라미터를 입력해야 하는 값에 Provider에서 제공하는 인스턴스를 Injection 하는 방식

Car.kt

const val TAG = "Dagger Car"

class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {

    fun drive() {
        Log.d(TAG, "나는 드라이버")
    }

    @Inject
    fun connectRemote(remote: Remote) {
        remote.enableRemote(this)
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    //메서드 주입(Method Injection)
    // -> 메서드의 파라미터 입력 값에 Provider에 제공하는 인스턴스를 주입하는 방식
    @Inject
    lateinit var car : Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
        val carComponent : CarComponent = DaggerCarComponent.create()
        carComponent.inject(this)
        car.drive()
    }
}

Remote.kt

class Remote @Inject constructor() {

    fun enableRemote(car: Car) {
        Log.d(TAG, "Remote is connected")
    }
}

Named Injection

  • Provider에서 제공하는 인스턴스의 타입이 깥을 때 어떤 타입의 인스턴스를 주입해서 쓸지 구분하여 결정 하는 것

CarComponent.kt


@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {

    fun getCar(): Car
    fun inject(mainActivity: MainActivity)

interface CarComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun horsePower(@Named("horsePower")hp: Int): Builder

        // 인스턴스의 타입이 같을 때 이름을 설정하여 주입 하는 것
        @BindsInstance
        fun engineCapacity(@Named("engineCapacity")cap: Int): Builder

        fun build(): CarComponent
    }

PetrolEngine.kt

//Named 으로 어떤 것을 주입 받아야 하는지 명확해짐
class PetrolEngine @Inject constructor(
    @Named("horsePower") val horsePower: Int,
    @Named("engineCapacity") val engineCapacity: Int
) : Engine {

  override fun start() {      
        Log.d(TAG, "Petrol engine started $horsePower hp, $engineCapacity capacity")
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var car : Car
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val carComponent = DaggerCarComponent
            .builder()
            .horsePower(150)
            .engineCapacity(1400)
            .build()
        carComponent.inject(this)
        
		car.drive()
    }
}

Scope

  • Container에서 제공하는 인스턴스들이 한번 만 생성 되도 되는 것을 부를때마다 계속 생성되어 메모리 누수가 생기는 것을 방지하기 위해서 쓰는 것

  • Scope를 지정하여 관리하는 것 범위를 결정

1. Singleton

  • 객체가 재사용되는 범위를 설정하기 위한 것 같은 객체를 쓰는 것

Car.kt

const val TAG = "Dagger Car"

class Car @Inject constructor(val wheels: Wheels, val engine: Engine, val driver: Driver) {

    fun drive() {
        engine.start()
        Log.d(TAG, "나는 $driver $this")
    }

    @Inject
    fun connectRemote(remote: Remote) {
        remote.enableRemote(this)
    }
}

Driver.kt

@Singleton
class Driver @Inject constructor() {}

CarComponent.kt

@Singleton
@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
    fun getCar(): Car
    fun inject(mainActivity: MainActivity)
 
    @Component.Builder
    interface Builder {
 
        @BindsInstance
        fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
        fun build(): CarComponent
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {
    
    @Inject
    lateinit var car1 : Car

    @Inject
    lateinit var car2 : Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        val carComponent = DaggerCarComponent
            .builder()
            .horsePower(150)
            .engineCapacity(1400)
            .build()
        carComponent.inject(this)

        car1.drive()
        car2.drive()

    }
}

2. 커스텀Scope

  • annotation class를 만들어 사용 말 그대로 커스텀 하는 것

Dagger2CarApplication.kt


class Dagger2CarApplication: Application() {
  
    lateinit var appComponent: ApplicationComponent

    override fun onCreate() {
        super.onCreate()

        appComponent = DaggerApplicationComponent.create()
     
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var car1: Car

    @Inject
    lateinit var car2: Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val carComponent = DaggerCarComponent
            .builder()
            .horsePower(120)
            .engineCapacity(1400)
            .getApplicationComponent((applicationContext as Dagger2CarApplication).appComponent)
            .build()

        carComponent.inject(this)

        car1.drive()
        car2.drive()
    }
}

ApplicationComponent.kt

@Singleton
@Component(modules = [DriveModule::class])
interface ApplicationComponent {
    fun getDriver(): Driver
}

CarComponent.kt

@PerActivity
@Component(dependencies = [ApplicationComponent::class] ,
    modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {

    fun getCar(): Car
    fun inject(mainActivity: MainActivity)
    
    @Component.Builder
    interface Builder {
       
        @BindsInstance
        fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
        fun getApplicationComponent(appComponent: ApplicationComponent): Builder
        fun build(): CarComponent
    }
}

Car.kt

@PerActivity
class Car @Inject constructor(val wheels: Wheels, val engine: Engine, val driver: Driver) {
    fun drive() {
        engine.start()
        Log.d(TAG, "나는 $driver $this")
    }
    @Inject
    fun connectRemote(remote: Remote) {
        remote.enableRemote(this)
    }
}

Driver.kt

class Driver {}

PerActivity.kt

//커스텀Scope 만들기
 /*Container에서 제공하는 인스턴스들이 한번만 생성되도 되는데 호출 될때마다 생성 되며 불필요한 메로리 사용이 생김
 이를 해결하기 위해 하는것이 @Scope
 한번 생성된 객체가 재사용되는 범위를 관리*/
// Scope는 객체가 재사용되는 범위만을 가리킬 뿐
@Scope
annotation class PerActivity {}

DriveModule.kt

@Module
class DriveModule {

    companion object {

        @JvmStatic
        @Singleton
        @Provides
        fun drive(): Driver = Driver()
    }
}

SubComponent

  • dagger2에서는 SubComponent를 생성할 수 있으며 SubComponent는 말 그대로 부모 Component가 있는 자식 Component

  • Inject로 의존성 주입을 요청받으면 SubComponent에서 먼저 의존성을 검색하고, 없으면 부모로 올라가면서 검색을 하고 SubComponentComponent와 달리 코드 생성은 부모 Component에서 이루어 짐

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var car1: Car
    @Inject
    lateinit var car2: Car
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        (applicationContext as Dagger2CarApplication).appComponent
            .getCarComponentBuilder()
            .horsePower(120)
            .engineCapacity(1400)
            .build()
            .inject(this)

        car1.drive()
        car2.drive()
    }
}

ApplicationComponent.kt

@Singleton
@Component(modules = [DriveModule::class])
interface ApplicationComponent {

    fun getCarComponent(dieselPetrolEngineModule: DieselPetrolEngineModule): CarComponent
    fun getCarComponentBuilder(): CarComponent.Builder
}

CarComponent.kt

@PerActivity
//여러 Component들을 쓸수 있게 의존 관게를 하고 상위에 프로바이더를 쓸 수 있도록 하는 것
@Subcomponent(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
    fun getCar(): Car
    fun inject(mainActivity: MainActivity)
   
    @Subcomponent.Builder
    interface Builder {

        @BindsInstance
        fun horsePower(@Named("horsePower")hp: Int): Builder
        @BindsInstance
        fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
        fun build(): CarComponent

    }
}

Binds

  • @Binds@Provides의 그냥 특수한 형태 이며 역활은 같다
  • Module에서 제공 하는 클래스의 구현체(interface 구현체, 객체)를 바인딩하고자 할 때 사용
  • 추상 클래스 및 추상 함수에 사용 가능하며 추상 클래스에 @Provider를 넣으려면 Companion object에만 가능

Car.kt

const val TAG = "Dagger Car"

class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {

    fun drive() {
        engine.start()
        Log.d(TAG, "나는 드라이버")
    }

    @Inject
    fun connectRemote(remote: Remote) {
        remote.enableRemote(this)
    }
}

CarComponent.kt

@Component(modules = [WheelsModule::class, PetrolEngineModule::class])//, DieselPetrolEngineModule::class])
interface CarComponent {
    fun getCar() : Car

    fun inject(mainActivity: MainActivity)
}

Engine.kt

interface Engine{
    fun start()
}

PetrolEngine.kt

class PetrolEngine @Inject constructor(): Engine {
    override fun start() {
        Log.d(TAG, "Petrol engine started")
    }
}

DieselPetrolEngineModule.kt

@Module
abstract class DieselPetrolEngineModule {

    @Binds
    abstract fun bindEngine(engine: DieselEngine): Engine
}

PetrolEngineModule.kt

//@Binds는 @Provides의 특수한 형태일 뿐 같은 역활을 함
// 추상 클래스와 추상 메서드에만 유효하며 반드시 하나의 매개 변수만을 가져와야 함
@Module
abstract class PetrolEngineModule {

    @Binds
    abstract fun bindEngine(engine: PetrolEngine): Engine
}

WheelsModule.kt

@Module
class WheelsModule {
    @Module
    companion object {
        @JvmStatic
        @Provides
        fun provideRims() = Rims()
        @JvmStatic
        @Provides
        fun provideTires(): Tires {
            val tires = Tires()
            tires.inflateTires()
            return tires
        }
        @JvmStatic
        @Provides
        fun provideWheels(rims: Rims, tires: Tires) = Wheels(rims, tires)
    }
}

BindsInstance

  • Component 내부에는 자체적으로 Builder 가 들어 있음
    하지만 이러한 Builder를 설정하기 하기위해 @BuindsInstance를 사용하여 Builder 를 설정하는 것

  • @BuindsInstance에는 Component빌더 안에 설정 되면 @BuindsInstance에 의해 제공된 모든 모듈에 제공이 가능 한것

MainActivity.kt

class MainActivity : AppCompatActivity() {
    
    @Inject
    lateinit var car : Car
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val carComponent = DaggerCarComponent
            .builder()
            .horsePower(150) 
            .build()
        carComponent.inject(this)

        car.drive()
    }
}

CarComponent.kt

@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
    fun getCar() : Car

    fun inject(mainActivity: MainActivity)

    // 별도의 Builder를 설정하지 않아도 Component내부에 Builder가 만들어져 있음
    // Builder를 따로 설정하기 위해서 Component.Builder를 선언
    // 기존 빌더와 달라진 점은
    // -> 빌더 내부에 메서드가 들어가고 해당 메서드를 통해 값을 세팅
    @Component.Builder
    interface Builder{

        // 특정 인스턴스를 Provide하는 @BindsInstance가 Component이 빌더안에 설정되면 해당 Component는
        // BindsInstance에 의해 제공된 Component 및 그 하위의 모든 모듈에 Provide가 가능
        // 그래서 PetrolEngineModule에 없어도 제공이 가능하게 된 것
        //TODO 그냥 Provider의 특수한 형태 @Bind 와 관련된 Annotation은 모두 Provider의 그냥 특수한 형태라고 함
        @BindsInstance
        fun horsePower(hp: Int): Builder
        fun build(): CarComponent
    }
}

PetrolEngine.kt

class PetrolEngine @Inject constructor(val horsePower: Int): Engine {
    override fun start() {
        Log.d(TAG, "Petrol engine started $horsePower hp")
    }
}

참고

https://mimisongsong.tistory.com/34?category=1231081
https://qiita.com/iTakahiro/items/c9203f5ad886b7ddae2f
https://developer88.tistory.com/173
https://jaejong.tistory.com/125
https://kotlinworld.com/111
profile
Study Note

0개의 댓글