Dagger2 #3 - @Named @Qualifier
Dagger 관련 글
- DI (Dependency Injection) - 의존성 주입
- Dagger2 #1 - 기본 개념
- Dagger2 #2 - Scope / Binds / MultiBinding
- Dagger2 #3 - @Named @Qualifier (현재 글)
- Dagger2 #4 - context 주입방법 @BindsInstance @Component.Builder @Component.Factory
- Dagger2 #5 - Android Dagger 사용방법 3가지
: HasAndroidInjector, DaggerApplication, DispatchingAndroidInjector - Dagger2 #6 - Retrofit + DaggerApplication Factory 예시
@Named
Dagger2에서 제공하는제공하는 @Named annotation입니다
때로는 자료형(Type)만으로는 의존성을 식별하기에 어려운 경우에 @Named annotation을 사용해 의존성을 주입합니다
@Named는 Dagger2에서 제공하는 annotation이지만 Named는 @Qualifier로 annotate 되어있습니다
@Named 정의는 아래와 같습니다
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
Dagger2에 제공되는 Named 클래스에서 중요한 점은 @Qualifier annotation입니다
@Qualifier는 javax.inject Package에서 제공되는 annotation으로 Named는 @Qualifier로 annotate 되어있는 구조입니다
즉, @Qualifier는 provides 메서드 구분을 위한 커스텀 Named annotation 클래스를 만들기 위한 주석이라고 생각하시면 됩니다
@Named가 필요한 이유
- 기본으로 @Inject 필드 변수에 의존성 주입하는 @provids 방법은 아래와 같습니다
class Cat(val name: String)
name 프로퍼티를 갖는 Cat이라는 class가 있다고 가정하겠습니다
@Module
class CatModule {
@Provides
fun provideGarfield(): Cat = Cat("Garfield")
@Provides
fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}
Cat의 의존성을 주입하는 CatModule입니다.
같은 Cat 타입을 반환하는 @Provides 메서드가 존재하는데, 서로 name프로퍼티가 다른 객체입니다
class MainAcitivty : AppCompatActivity() {
@Inject
lateinit var garfield: Cat
@Inject
lateinit var helloKitty: Cat
override fun onCreate(savedInstanceState: Bundle?) {
...
DaggerCatComponent.create().inject(this@MainActivity)
Log.d("test", "Cat name is ${garfield.name}")
Log.d("test", "Cat name is ${helloKitty.name}")
}
}
이제 Cat 의존성 주입을 해보겠습니다
결과는 아래와 같습니다
error: [Dagger/DuplicateBindings]
packagename.something.something.Cat is bound multiple times:
이유는 CatModule의 @Provides 메서드인 provideGarfield() 와 provideHelloKitty() 모두 같은 Cat Type이기 때문에 Dagger는 어떤 메서드로 의존성 주입을 할지 알 수 없게 되므로 오류를 발생합니다
이렇게 같은 반환Type(여기선 Cat)의 @Provides 메서드가 여러개가 존재할 경우 구분을 위해 @Named annotation을 사용해서 위와 같은 문제를 해결합니다
- 동일 반환Type의 @Provides 메서드를 사용하기위한 @Named annotation 사용
@Module
class CatModule {
@Provides
@Named("Garfield")
fun provideGarfield(): Cat = Cat("Garfield")
@Provides
@Named("HelloKitty")
fun provideHelloKitty(): Cat = Cat("Hello Kitty")
}
CatModule의 의존성주입 메서드인 @Provides 메서드에 @Named annotation으로 메서드구분을 위한 이름을 선언합니다
해당 의존성주입 메서드를 사용하려면 @Inject 필드에 @field:Named("name")을 같이 선언해줘서 Dagger가 동일 Type의 @Provides 메서드 중 구분이 가능합니다
class MainAcitivty : AppCompatActivity() {
@Inject
@field:Named("Garfield")
lateinit var garfield: Cat
@Inject
@field:Named("HelloKitty")
lateinit var helloKitty: Cat
ovverride fun onCreate(savedInstanceState: Bundle?) {
...
DaggerCatComponent.create().inject(this@MainActivity)
Log.d("test", "Cat name is ${garfield.name}")
Log.d("test", "Cat name is ${helloKitty.name}")
}
}
@Inject로 의존성 주입을 할 멤버변수에 @field:Named() annotation을 사용함으로 Module의 동일한 반환Type의 @Provides 메서드들 중에서 선택해서 의존성 주입을 받을 수 있게됩니다
@field:Named("HelloKitty")는 provideHelloKitty() 를 의미합니다
즉, @Named annotation은 같은 종류의 반환Type을 가진 Provides 메서드들 중에서 선택해서 종속성을 주입하기 위해 사용하는 annotation입니다
Custom Named Class
커스텀 @Named을 사용하기 위해 Custome Named Class를 생성해보겠습니다
Custom Scope를 정의하는 방법처럼 annotation class로 선언해줍니다
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedClone(val value: String = "")
Custom Named 클래스입니다. 해당 클래스는 annotation class로 선언해줘야 합니다
@Provides
@NamedClone("Red Apple")
fun provideRedApple(): Apple = Apple("red")
@Provides
@NamedClone("Green Apple")
fun provideGreenApple(): Apple = Apple("green")
의존성 주입을 하는 Module내부 provides 메서드입니다
두 메서드 모두 Apple Tpye을 반환하며, 메서드 별로 @NamedClone()으로 이름을 붙여놨습니다
@Inject
@field:NamedClone("Red Apple")
lateinit var redApple: Apple
@Inject
@field:NamedClone("Green Apple")
lateinit var greenApple: Apple
사용방법은 이전 @Named 에서 선언한 Custom Named인 @NamedClone 으로 변경된 차이점입니다
Enum을 사용하는 Custom Named
위의 NamedClone 클래스의 프로퍼티인 value가 String타입으로 느슨한 문자열에 의존합니다
느슨한 문자열? @Named로 설정된 문자열에 대문자 소문자가 모두 일치해야합니다
Red Apple != red apple 의미
이 방법은 오류가 발생하기 쉽기 때문에 Enum을 사용하도록 수정하겠습니다
먼저 기존의 사용방법일 경우 오류가 발생하는 예시입니다
@Provides
@NamedClone("Red Apple")
fun provideRedApple(): Apple = Apple("red")
@Inject
@field:NamedClone("red apple")
lateinit var redApple: Apple
위와 같이 NamedClone의 value로 Red Apple을 소문자로 red apple이라 입력할 경우 컴파일에서 오류를 발생합니다
Red Apple != red apple 이기 때문입니다
그럼 아래는 String을 Enum으로 변경하겠습니다
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedClone(val color: AppleType)
// Red, Green 두개의 Type만 가진 Enum 클래스
enum class AppleType {
Red, Green
}
NamedClone의 프로퍼티 Type을 String에서 Enum 클래스 타입인 AppleType으로 변경하였습니다
AppleType은 Red, Green 두개의 값만 가진 Enum 클래스로, 그 와의 값을 입력 시 구문오류가 발생합니다
@Provides
@NamedClone(AppleType.Red)
fun provideRedApple(): Apple = Apple("red")
@Provides
@NamedClone(AppleType.Green)
fun provideGreenApple(): Apple = Apple("green")
@Inject
@field:NamedClone(AppleType.Red)
lateinit var redApple: Apple
@Inject
@field:NamedClone(AppleType.Green)
lateinit var greenApple: Apple
Enum Type을 사용하도록 변경하였습니다.
이제 @NamedClone annotation에 Enum 클래스인 AppleType의 값이 아닌 다른 값을 입력하면 구문오류가 즉시 발생하기 때문에, 컴파일 전에 수정이 가능합니다