07. 컴포넌트
- 바인딩된 모듈로 부터 오브젝트 그래프를 생성하는 핵심적인 역할
@Component
사용interface
,abstract class
에만 붙일 수 있음
- 컴파일 타임에 'Dagger',
@Component
가 붙은 클래스 이름이 합쳐진 형식의 이름을 가짐 @Component
가 갖는 속성modules
: 컴포넌트에 바인드되는 @Module 이 지정된 클래스 배열- 모듈이 다른 모듈을 포함한 경우 해당 모듈까지 컴포넌트에 구현해야함
dependencies
: 컴포넌트에 다른 컴포넌트의 의존성을 사용하는 경우 클래스 배열
오브젝트 크래프
- Dagger에서는 컴포넌트, 모듈, 객체 등의 관계를 컨테이너 or (오브젝트)그래프 라고 함
[Hello World 예제의 그래프]
컴포넌트 매서드
@Component
가 붙은 모든 타입이 하나 이상 가져야 하는 메서드- 메서드 이름은 상관없으나 반환 타입 규칙은 무조건 따라야 한다.
- 프로비전 메서드와 멤버-인젝션 메서드로 구분된다.
프로비전 메서드(Provision Methods)
- Dagger의 컴포넌트에서 매개변수가 없고, 반환형이 모듈로 부터 제공 또는 주입되는 메서드
@Module
class SomeModule {
@Provides
fun provideSomeType(): SomeType = SomeType()
}
class SomeType {}
@Component(modules = [SomeModule::class])
interface SomeComponent {
fun getSomeType(): SomeType // SomeModule 로 부터 제공 또는 주입 받은 SomeType 객체를 반환
}
멤버-인젝션 메서드(Member-injection methods)
- Dagger의 컴포넌트에서 하나의 매개 변수를 갖는 메서드.
- 멤버-인젝션 메서드는 unit을 반환 또는 빌더 패턴 처럼 메서드 체이닝 가능한 메서드를 만들기 위해 매개 변수 타입을 반환 형으로 갖느 메서드로 선언
잠깐 빌더 패턴이 뭔지 알아보자
- 빌더 패턴(Builder pattern)이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다.
var customer: Member = Member.build()
.name("홍길동")
.age(30)
.build()
- 멤버-인젝션 메서드 예시
@Component(modules = [SomeModule::class]) interface SomeComponent { fun injectSomeType(someType: SomeType) fun injectAndReturnSomeType(someType: SomeType): SomeType }
- 실제로 테스트 해보기
class MyClass {
var str: String? = null
@Inject set
}
@Module
class MyModule {
@Provides
fun provideHelloWorld(): String = "Hello World"
@Provides
fun provideInt(): Int = 1234
@Provides
fun providePerson(name: String, age: Int): Person = Person(name, age)
}
@Component(modules = [MyModule::class])
interface MyComponent {
fun getString(): String
fun getInt(): Int
fun getPerson(): Person
fun inject(myClass: MyClass) // 멤버-인젝션 메서드
}
- 테스트
class ExampleUnitTest {
@Test
fun test_memberInjection(){
val myClass = MyClass()
var str = myClass.str
assertNull("조회 결과 null", str)
val myComponent = DaggerMyComponent.create()
myComponent.inject(myClass)
str = myClass.str
assertEquals("Hello World", str)
}
}
myComponent.inject(myClass)
전에는 null 이었다가 이후에 "Hello World"가 주입된 것을 알 수 있다.- 매개 변수 없이
MemberInjector<T>
를 반환할 수도 있다.- 반환된
MemberInjector
객체의injectmembers(T)
메서드를 호출하면 멤버-인잭션 메서드와 동일 작업을 수행한다.
- 반환된
@Component(modules = [MyModule::class])
interface MyComponent {
...
fun getInjector():MembersInjector<MyClass>
}
class ExampleUnitTest {
@Test
fun tet_memberInjector(){
val myClass = MyClass()
var str = myClass.str
println("result = $str") // null
val myComponent = DaggerMyComponent.create()
val injector: MembersInjector<MyClass> = myComponent.getInjector()
injector.injectMembers(myClass)
str = myClass.str
println("result = $str") // Hello World
}
}
의존성 주입하기
- Dagger 3가지 의존성 주입 방법
- 필드 주입
- 생성자 주입
- 메서드 주입
@Inject
어노테이션이 붙은 필드, 메서드 그리고 생성자에 인스턴스를 주입- 실무에서는 주로 필드, 생성자 주입 사용
[PersonModule.kt] : 이름과 나이를 제공
@Module
class PersonModule {
@Provides
fun provideName(): String = "SangSuLee"
@Provides
fun provideAge(): Int = 100
}
[PersonComponent.kt]
@Component(modules = [PersonModule::class])
interface PersonComponent {
fun getPersonA():PersonA // 프로비전 매서드 (PersonA 객체를 제공)
fun inject(personB: PersonB) (PersonB에 멤버-인젝션)
}
[PersonA.kt]
//생성자 주입
class PersonA @Inject constructor(val name: String, val age: Int)
[PersonB.kt]
class PersonB {
@Inject // 필드 주입
var name: String? = null
@set:Inject // 메서드 주입 (setter)
var age = 0
}
- 테스트 코드
class ExampleUnitTest {
@Test
fun test_Injection(){
val personComponent = DaggerPersonComponent.create()
val personA = personComponent.getPersonA()
println("${personA.name} : ${personA.age}") // SangSuLee : 100
val personB = PersonB()
DaggerPersonComponent.create().inject(personB)
assertEquals("SangSuLee", personB.name)
assertEquals(100, personB.age)
}
}
상속된 클래스에 의존성 주입
- 멤버-인젝션 메서드를 호출할 때
- 매개 변수 타입에 서브 클래스의 객체를 넣으면
- 해당 슈퍼 클래스의
@Inject
멤버만 의존성 주입이 된다.
open class Parent {
var a: A? = null
@Inject set
}
open class Self : Parent() {
var b: B? = null
@Inject set
}
class Child : Self() {
var c: C? = null
@Inject set
}
- 예시로
- 컴포넌트에 멤버-인젝션 메서드인
fun inject(Self)
가 존재하고 - Child 의 인스턴스를 멤버-인젝션 메서드의 매개변수로 참조하여 호출하면
- Child의 인스턴스 에는 a 하고 b 만 주입되며 c에는 주입되지 않는다.
컴포넌트 객체 만들기
- 생성된 빌더 또는 팩토리를 통해 만들 수 있음
- 컴포넌트 내의
@Component.Builder
또는@Component.Factory
타입 선언을 통해 빌더 혹은 팩토리 생성 - 빌더, 팩토리 어노테이션이 둘 다 없으면
@Component
어노테이션에 선언된 모듈 및 의존성을 참조하여 빌더를 자동으로 생성
@Component(modules = [MyModule::class])
interface MyComponent {
...
@Component.Builder
interface Builder{
fun setMyModule(myModule: MyModule): Builder
fun build():MyComponent
}
}
컴포넌트 빌더를 만드는 조건
@Component.Builder
어노테이션은 컴포넌트 타입 내에 선언되어야 함- 반드시 매개변수 가없고 컴포넌트 타입 또는 컴포넌트의 슈퍼 타입을 반환하는 추상 메서드를 하나 포함 해야함 (이를 빌드 메서드 라고 함)
- 빌드 메서드를 제외한 나머지는 세터 메서드(setter methods)라고 함
@Component
어노테이션에 modules, dependencies로 선언된 요소들은 세터 메서드로 선언 해야함- 세터 메서드는 반드시 하나의 매개 변수만 가져야 하며, 반환 형으로는 unit, 빌더 또는 빌더의 슈퍼 타입이 될 수 있음
- 세터 메서드에
@BindsInstance
를 붙이면, 해당 컴포넌트에 인스턴스를 넘겨 바인드 시킨다.
[위 조건을 만족하는 컴포넌트 빌더의 예시]
@Component(modules = [BackendModule::class, FrontendModule::class])
interface ExampleComponent{
fun myWidget(): MyWidget
@Component.Builder
interface Builder {
fun backendModule(bm: BackendModule): Builder
fun frontendModule(fm: FrontendModule): Builder
@BindsInstance
fun foo(foo: Foo): Builder)
fun build(): MyComponent
}
}
컴포넌트 팩토리를 만드는 조건
@Component.Factory
어노테이션은 컴포넌트 타입 내에 선언되어야 한다.- 컴포넌트 팩토리는 컴포넌트 타입 또는 컴포넌트의 슈퍼 타입을 반환하는 하나의 추상 메서드만 존재해야 한다.
- ex.
newInstance()
- ex.
- 팩토리 메서드에는
@Component
어노테이션에modules
,dependencies
로 지정된 속성들을 반드시 매개변수로 가져야 한다. - 메서드에
@BindsInstance
어노테이션이 붙은 매개 변수는 해당 컴포넌트에 인스턴스를 넘겨 바인드 시킨다.
@Component(modules = [BackendModule::class, FrontendModule::class])
interface ExampleComponent {
fun myWidget(): MyWidget
@Component.Factory
interface Factory {
fun newMyComponent(
bm: BackendModule,
fm: FrontendModule,
@BindsInstance foo: Foo
): MyComponent
}
}
- 생성되는 컴포넌트 형식에는 factory() 라는 정적 메서드를 갖게됨
- 팩토리 인스턴스를 반환, 이 팩토리 인스턴스로 컴포넌트를 초기화할 수 있음
2021.07.21 - [Android/클린 아키텍처] - [Clean Architecture] 07-Dagger2의 모듈(Module)
2021.07.20 - [Android/클린 아키텍처] - [Clean Architecture] 06-Dagger2 란? (+ Adnroid 적용 샘플)
2021.07.19 - [Android/클린 아키텍처] - [Clean Architecture] 05-의존성 주입(DI)과 그 필요성
해당 글은 '아키텍처를 알아야 앱 개발이 보인다' 를 공부하며 요약 정리한 글 입니다.
'Android > 클린 아키텍처' 카테고리의 다른 글
[Clean Architecture] 10-한정자 (@named) (0) | 2021.07.24 |
---|---|
[Clean Architecture] 09-Lazy 주입 & Provider 주입 (dagger) (0) | 2021.07.23 |
[Clean Architecture] 07-Dagger2의 모듈(Module) (0) | 2021.07.21 |
[Clean Architecture] 06-Dagger2 란? (+ Adnroid 적용 샘플) (0) | 2021.07.20 |
[Clean Architecture] 05-의존성 주입(DI)과 그 필요성 (0) | 2021.07.19 |