반응형
이번 포스트에서 다룰 내용은 Qualifier 어노테이션입니다. 이름 그대로 객체를 식별해주는 어노테이션입니다.
@Named Qualifer
Dagger는 의존성 주입을 요청했을 때 Module에 선언된 자료형(Type)과 동일한 객체를 주입해줍니다.
따라서, Module에 동일한 Type의 객체가 여러개 선언되어 있을 경우, 컴파일 타임에 에러를 발생하게 됩니다.
의존성주입은 Interface를 기반으로하는 코드를 권장하므로, 같은 부모를 가진 여러 Child 클래스가 존재할 가능성이 매우 높습니다.
이러한 경우를 위해서 @Named 어노테이션을 통해서 같은 타입의 여러 객체를 선언하고 주입해줄 수 있습니다.
사용법은 간단합니다. Module의 @Provides어노테이션과 @Injection 어노테이션에 각각 @Named 어노테이션을 통해 어떠한 객체를 주입할 건지 선언해 주면 Module에 @Named로 선언된 객체와 동일한 객체를 주입해줄 수 있습니다.
ComputerComponent.kt
@Component(modules = [ComputerModule::class])
interface ComputerComponent {
fun getComputerB(): ComputerB // Provision Method
fun inject(computerA: ComputerA) // Member - Injection
@Component.Builder
interface Builder {
fun computerModule(computerModule: ComputerModule): Builder
fun application(@BindsInstance context: Context): Builder
fun build(): ComputerComponent
}
}
ComputerModule.kt
@Module
class ComputerModule {
@Named("M1")
@Provides
fun provideM1Cpu(): Cpu {
println("create Cpu")
return M1Cpu()
}
@Named("I7")
@Provides
fun provideI7Cpu(): Cpu {
println("create Cpu")
return I7Cpu()
}
@Named("Ram4G")
@Provides
fun provideRam4G(): Ram {
println("create Ram")
return Ram4G()
}
@Named("Ram8G")
@Provides
fun provideRam8G(): Ram {
println("create Ram")
return Ram8G()
}
}
Cpu.kt
interface Cpu {
val name: String
val company: String
}
class I7Cpu : Cpu {
override val name: String
get() = Name
override val company: String
get() = "Intel"
companion object {
const val Name = "I7 CPU"
}
}
class M1Cpu : Cpu {
override val name: String
get() = "M1 Chip Cpu"
override val company: String
get() = "Apple"
companion object {
const val Name = "M1 Chip Cpu"
}
}
Ram .kt
interface Ram {
val name: String
val company: String
}
class Ram4G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Samsung"
companion object {
const val Name ="4G RAM"
}
}
class Ram8G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Hynix"
companion object {
const val Name ="8G RAM"
}
}
class ComputerA {
@Named("M1")
@Inject
lateinit var cpu: Cpu
@Named("Ram4G")
@Inject
lateinit var ram: Ram
}
class ComputerB @Inject constructor(@Named("I7") val cpu: Cpu, @Named("Ram8G") val ram: Ram)
Test Code
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
private lateinit var computerComponent: ComputerComponent
private lateinit var context: Context
@Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().targetContext
computerComponent = DaggerComputerComponent.builder().application(context).build() // Component Builder
}
@Test
fun testComputerMemberInjection() {
val computerA =
ComputerA()
computerComponent.inject(computerA) // Field Inject
Assert.assertEquals(M1Cpu.Name, computerA.cpu.name)
println("computerA's cpu = ${computerA.cpu.name}")
println("computerA's ram = ${computerA.ram.name}")
}
@Test
fun testComputerConstructorInject() {
val computerB = computerComponent.getComputerB() // Constructor Inject
Assert.assertEquals(I7Cpu.Name, computerB.cpu.name)
println("computerB's cpu = ${computerB.cpu.name}")
println("computerB's ram = ${computerB.ram.name}")
}
}
Test 결과


사용자 정의 Qualifer
사용자 정의 식별자는 @Named 어노테이션에 String을 넣어주었던 것과는 다르게 직접 어노테이션을 생성해서 주입해줄 수 있습니다.
사용법은 @Named와 동일하게 Module의 @Provides와 @Inject에 각각에 정의했던 어노테이션을 선언해주면 해당하는 객체를 주입받을 수 있습니다.
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetM1Cpu
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetI7Cpu
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetRam4G
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetRam8G
@Module
class ComputerModule {
@GetM1Cpu
@Provides
fun provideM1Cpu(): Cpu {
println("create Cpu")
return M1Cpu()
}
@GetI7Cpu
@Provides
fun provideI7Cpu(): Cpu {
println("create Cpu")
return I7Cpu()
}
@GetRam4G
@Provides
fun provideRam4G(): Ram {
println("create Ram")
return Ram4G()
}
@GetRam8G
@Provides
fun provideRam8G(): Ram {
println("create Ram")
return Ram8G()
}
}
class ComputerA {
@GetM1Cpu
@Inject
lateinit var cpu: Cpu
@GetRam4G
@Inject
lateinit var ram: Ram
}
class ComputerB @Inject constructor(@GetI7Cpu val cpu: Cpu, @GetRam8G val ram: Ram)
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
private lateinit var computerComponent: ComputerComponent
private lateinit var context: Context
@Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().targetContext
computerComponent = DaggerComputerComponent.builder().application(context).build() // Component Builder
}
@Test
fun testComputerMemberInjection() {
val computerA =
ComputerA()
computerComponent.inject(computerA) // Field Inject
Assert.assertEquals(M1Cpu.Name, computerA.cpu.name)
println("computerA's cpu = ${computerA.cpu.name}")
println("computerA's ram = ${computerA.ram.name}")
}
@Test
fun testComputerConstructorInject() {
val computerB = computerComponent.getComputerB() // Constructor Inject
Assert.assertEquals(M1Cpu.Name, computerB.cpu.name)
println("computerB's cpu = ${computerB.cpu.name}")
println("computerB's ram = ${computerB.ram.name}")
}
}


반응형