Dagger2를 알아보자 – 기본편 (You’re here)
Dagger2를 알아보자 – Scope 
Dagger2를 알아보자 – Injection의 종류
Dagger2를 알아보자 – Qualifier
Dagger2를 알아보자 – Binding
Dagger2를 알아보자 – Multibinding
Dagger2를 알아보자 – SubComponent
Dagger2를 알아보자 – Android
Dagger2를 알아보자 – Testing(준비중)
Dagger2를 알아보자 – Dynamic Feature에 적용하기


Dagger2란 무엇일까요?

Dagger는 자바와 Android를 위해 완전히 정적으로 컴파일 타임 의존성 주입 프레임 워크입니다. 

의존성 주입(Dependency-injection)이란?

의존성 주입은 프로그래밍에서 구성요소간의 의존 관계가 내부가 아닌 외부를 통해 정의되게 하는 디자인 패턴 중의 하나입니다. 의존성 주입의 목적은 객체를 생성하고 사용하는 관심사를 분리하는 것입니다.

위의 그림을 살펴보면 첫번째 그림은 A내에서 B를 생성하지만, 두번째 그림은 외부에서 B객체가 생성되어 A로 주입되는 모습입니다.

이때 외부에서 객체를 관리하게 되는데 이를 IOC(inversion of Control, 제어의 역전)라 합니다. 

Note : IOC는 객체의 생성부터 생명주기 관리까지 컨테이너에 의해 제어 되는 것을 의미하고, 의존성 주입(DI)은 객체간의 의존성을 자기 자신이 아닌 외부에서 주입받는 개념입니다.

 

의존성주입은 왜 필요할까?

의존성 주입은 다음과 같은 장점을 갖기 때문에 필요합니다.

  • 코드가 유연해짐, 클래스간의 결합도를 줄임.
  • 리팩토링이 수월함
  • 유닛테스트를 쉽게 수행 있음
  • 코드의 재사용성 증대
  • 보일러 플레이트 코드 감소
  • 스코프를 이용한 객체 관리

반면 단점도 있는데, 의존성 주입을 위한 선행작업이 필요하고 코드를 추적하고 읽기가 어려워집니다.

장점이 더 크기 때문에 단점은 무시해버립시다!

Dagger2 설정하기

Dagger2 설정은 공식 github에서 확인하시기바랍니다.

링크:https://github.com/google/dagger

Dagger2 사용하기

Dagger2는 리플렉션을 사용하지 않고 컴파일 타임에 코드를 생성해내기 때문에 빠릅니다. 컴파일 타임에 코드를 생성할 수 있는 이유는 자바의 Annotation Processing 덕분인데요. Dagger2에서 주로 사용하는 Annotation에 대해서 먼저 설명해보도록 하겠습니다.

Note :  자바의 Annotation에 대해서 더 자세히 알고 싶다면 Annotation Processor에 대한 포스트를 참조해주세요.

@Module 과 @Provide

@Module은 클래스에만 붙이며 @Provide는 반드시 @Module클래스안에 선언된 메소드에만 붙입니다.

Module클래스는 의존성 주입에 필요한 객체들을 Provide메소드를 통해 관리 합니다. Provide 메소드의 파라미터 또한 컴포넌트 구현체로부터 전달 받을 수 있습니다.

일반적으로 Module클래스는 클래스이름 뒤에 Module을 붙이고, Provide메소드명 앞에는 provide를 붙이는 것이 일반적인 컨벤션입니다.

@Module
public class AModule {
    @Provides
    AA provideAA(){ //AA 오브젝트를 컴포넌트에게 제공
        return new AA();
    }

    @Provides
    BB provideBB(AA aa){ //위에서 제공하는 AA를 컴포넌트로 부터 인자로 전달받아 BB를 제공
        return new BB(aa);
    }
}
@Module
public class BModule {
    @Provides
    String provideName(){
        return "Charles";
    }
}

Note : Dagger에서는 기본적으로 null을 인젝션하는 것을 금지 하고 있습니다. null을 인젝션 하고 싶다면 @Nullable 애노테이션을 이용해야합니다. @Provide 메소드와 객체를 주입받을 타입이 모두 쌍으로@Nullable 애노테이션이 붙어 있는 경우에만 null 주입을 허용하며, 그 이외의 경우에는 컴파일 타임에 에러를 발생 시킵니다.

@Inject

@Inject는 필드, 생성자, 메소드에 붙여  컴포넌트로부터 객체를 주입받을 수 있게 합니다. 객체를 주입받기 때문에 객체의 생성이 클래스에 의존적이지 않고 보일러플레이트코드를 작성할 필요없이 클래스를 테스트하기가 수월해 집니다.

public class AA {
}

public class BB {
    public BB(AA aa){
    }
}

public class CC {
    AA aa;
    BB bb;
    @Inject
    public CC(AA aa, BB bb){//생성자에 주입하는 예제
        this.aa = aa;
        this.bb = bb;
    }
}
public class Main{ //필드에 주입하는 예제
    @Inject AA aa; 
    @Inject BB bb; 
    @Inject CC cc;
    ...
}

기본적으로 Dagger는 요청된 자료형에 맞게 Module로부터 인스턴스를 생성하여 주입을 받게 됩니다. 모든곳에서 @Inject가 동작하는것은 아닙니다. @Inject를 붙일 수 없는 경우는 다음과 같습니다.

  • 인터페이스는 생성자가 없으므로 불가능
  • 써드파티 라이브러리 등의 클래스는 참조가 불가능하여 애노테이션프로세싱이 안됨
  • 기타 등등

@Component

@Component는 interface 또는 abstract 클래스에 붙일수 있습니다. 컴파일 타임에 애노테이션 프로세서에 의해 생성된 클래스는 접두어 ‘Dagger’와 @Component가 붙은 클래스이름이 합쳐진 형식의 이름을 갖습니다. 예를들면 @Component interface MyComponent{…}란 인터페이스가 있다면 DaggerMyComponent라는 클래스가 생성되게 됩니다.

Component methods

@Component 애노테이션이 달린 interface 또는 abstract 클래스에는 적어도 하나의 추상 컴포넌트 메소드가 있어야 합니다. 컴포넌트 메소드에는 Provision 메소드Member-Injection 메소드가 있습니다.

@Component(modules = {AModule.class, BModule.class})
public interface MyComponent {
    //provision 메소드
    AA makeAA();

    //member-injection 메소드
    void inject(Main target);

}
Provision Method

프로비전 메소드에는 매개변수가 없고, 모듈이 제공하는 객체의 타입을 반환형으로 갖습니다. 생성된 컴포넌트 클래스에서 이 메소드를 이용하여 객체를 얻을 수 있습니다.

Member-Injection Method

의존성을 주입시킬 객체를 메소드의 파라미터로 넘기는 방법입니다. 멤버인젝션 메소드를 호출 하게 되면 타겟 클래스 내의 @Inject 필드에 객체를 주입받게 됩니다.

@Component.Builder

컴포넌트를 생성하기 위한 Builder용 애노테이션입니다. Component 내의 abstract 클래스 또는 interface에 이 애노테이션을 붙입니다. 다음과 같이 Builder를 정의 할 수 있습니다.

@Component(modules = AModule.class)
public interface MyComponent {
    @Component.Builder
    interface Builder{
        MyComponent build();  
        Builder aModule(AModule module);
    }
}

Builder는 반드시 Component를 반환하는 메소드와 Builder를 반환하면서 컴포넌트가 필요로 하는 모듈을 파라미터로 받는 메소드를 가지고 있어야합니다. 이 조건을 충족하지 못하면 컴파일 타임에 에러가 발생합니다.

@Component.Builder 애노테이션을 지정하지 않으면 암시적으로 Builder가 생성됩니다.

Note: @Component.Builder 애노테이션을 사용하지 않으면, 생성되는 DaggerMyComponent에서 다음과 같은 코드가 포함되는것을 확인할 수 있습니다.

public final class DaggerMyComponent implements MyComponent {
    ...

    public static Builder builder() {
        return new Builder();
    }

    public static MyComponent create() {
        return new Builder().build();
    }
}

 

Dagger의 Graph

이제 Dagger를 통한 DI를 구현하기 위한 기본적인 준비는 끝났습니다. 의존성 주입 요청시, 모듈로부터 생성된 객체를 컴포넌트를 통해 주입받습니다. 위의 그림을 보면 조금 더 이해가 쉬울 것입니다. 위와 같이 모듈과 컴포넌트 그리고 의존성 주입받는 대상관의 계약관계를 Dagger에서는 그래프라고 합니다.

이제 실제로 컴포넌트를 통해 의존성 주입을 실행해보도록 하겠습니다.

public class Main{ //필드에 주입하는 예제
    @Inject AA aa;
    @Inject BB bb;
    @Inject CC cc;

    public Main(){
        DaggerMyComponent.create().inject(this);
    }
}

 

TL;DR

다시 한번 DI의 장점을 정리하자면,

관심사를 분리했으므로 객체를 생성하는 부분과 아닌 부분이 나뉘어졌으며, 리팩토링유닛테스트가 가능해집니다. 예를 들면 AA, BB, CC에 전혀 다른 객체를 주입하고 싶은 경우 객체를 생성하는 모듈만 변경해주면 됩니다. 

모듈 코드를 재사용할 수 있으니 보일러 플레이트 코드도 줄어듭니다.

위 예제는 github에서 확인가능합니다.

카테고리: Dagger2Java

14개의 댓글

익명 · 2019년 10월 6일 8:22 오후

좋아요

    Charlezz · 2019년 10월 7일 11:27 오전

    🙂

나비이쁜이 · 2019년 10월 7일 2:46 오후

계속 공부중인데 크게 도움이 되고 있습니다. 감사합니다!

    Charlezz · 2019년 10월 9일 10:03 오전

    Keep going! 🙂

정혜진 · 2019년 12월 27일 11:59 오전

좋은 자료 항상 감사드립니다~!

    Charlezz · 2019년 12월 28일 12:31 오후

    감사합니다~~~:)

과자공장 · 2020년 2월 26일 5:44 오후

dagger 관련 글중에서 최고로 쉽게 설명 해주셨네요
잘 보고 감사합니다.

    Charlezz · 2020년 4월 15일 8:22 오전

    감사합니다 🙂

BONI · 2020년 4월 14일 12:03 오후

Dagger 책 찾다가 여기까지 굴러 들어 왔는데 여기가 금맥 이었네요 ㅎㅎ

    Charlezz · 2020년 4월 15일 8:22 오전

    감사합니다 🙂

마가렛 · 2020년 4월 16일 5:08 오후

설명 대박이네요..

    Charlezz · 2020년 4월 17일 12:31 오후

    감사합니다!

개발자A · 2020년 6월 14일 11:44 오후

자세한 설명 감사합니다! 사실은 책도 사서 보고 있는데 책보다 블로그가 더 쉬운것 같아요.ㅠㅠ 책에는 Module, Provide 컨벤션에 대한 언급이 안되어 있어서 왜 클래스와 메소드에 Module, Provide가 있는거야? 하면서 한참을 생각했는데 여기 블로그에 언급해주셨네요. ㅎㅎ

개발자B · 2021년 4월 13일 1:33 오후

프로비전 메소드에는 매개변수가 없고 -> 라고 써있는데요.. 매개변수가 있고 그것도 inject 가능하면 쓸 수 있지 않나요? 잘 몰라서 문의드립니다 ^^

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.