2.Dagger를 이용한 주입 기법 -3

Jamezz Dev·2020년 7월 13일
0

android-Architecture

목록 보기
4/8

7.컴포넌트

컴포넌트는 바인딩된 모듈로부터 오브젝트 그래프를 생성하는 핵심적인 역할을 한다.

@Component를 사용하여 컴포넌트를 생성할수 있으며 interface 또는 abstract 클래스에만 붙일 수 있다.

컴파일 타입의 애노테이션 프로세서에 의해 생성된 클래스는 접두어 Dagger 와 @Component가 붙은 클래스 이름이 합쳐진 형식의 이름을 갖는다

오브젝트 그래프

Dagger 에서는 컴포넌트, 모듈 ,객체등의 관계를 컨테이너 또는 오브젝트 그래프라고 한다.

컴포넌트 메서드

@Component가 붙은 모든 타입은 최소한 하나의 추상적인 메서드를 가져야 한다.
메서드의 이름은 상관 없지만 메서드 매개변수와 반환형은 규칙을 엄격하게 따라야 한다

프로비전 메서드와 멤버-인젝션 메서드로 구분된다.

프로비전 메서드

Dagger의 컴포넌트에서 매개변수를 갖지 않으면서 반환형은 모듈로부터 제공되거나 주입되는 메서드를 말한다

@Component(modules = SomeModule.class)
public interface SomeComponent{
	SomeType getSomeType();
}

getSomeType() 메서드 호출시 SomeModule로부터 제공받거나 주입받은 SomeType 객체를 반환한다.

멤버-인젝션 메서드

Dagger의 컴포넌트에서는 하나의 매개변수를 갖는 메서드를 멤버-인젝션 메서드라고 한다.

void를 반환하거나 빌더 패턴처럼 메서드 체이닝이 가능한 메서드를 만들기 위해 매개변수 타입을 반환형으로 갖는 메서드로 선언할 수 있다.

@Component(modules = SomeModule.class)
public interface SomeComponent{
    void injectSomeType(SomeType someType);
    someType injectAndReturnSomeType(SomeType someType);
}

멤버-인젝션 메서드를 구현해보자
의존성을 주입 받도록 MyClass 를 생성한다 의존성 주입을 받을 필드 메서드에 @Inject 애노테이션을 붙인다

public class MyClass{
    
    @Inject
    String str;

    public String getStr(){
    	return str;
    }
}

MyComponent에도 멤버-인젝션 메서드를 추가한다

@Component(modules = MyModule.class)
public interface MyComponent{
	void inject(MyClass myClass);
}

멤버 인젝션이 잘 되는지 확인하려면 다음과 같이 테스트 해본다

@Test
public void testMemberInjection(){
    MyClass myclass = new MyClass();
    String str = myclass.getStr();
    assertNotNull("조회결과 null " str); // null 이 아님을 확인
    
    Mycomponent myComponent = DaggerMyComponent.create();
    myComponent.inject(myclass);
    str = myclass.getStr();
    assertEquals("hello world",str)//str = hello world
}

위의 예제를 통해 myClass 필드가 null 이었다가 메서드 호출 이후에는 hello world가 주입된것을 확인 할 수가 있다.

매개변수가 없고 MembersInjector<T>를 반환할 수도 있다 이 경우에는 반환된 MemberInjector 객체의 injectmembers(T)메서드를 호출하면 멤버-인젝션 메서드와 동일한 작업을 수행한다.

@Component(modules = MyModule.class)
public interface MyComponent{
...
MembersInjector<MyClass> getInjector();
}

@Test
public void testMemberInjector(){
    Myclass myclass = new Myclass();
    String str = myclass.getStr();
    System.out.println("result= "+str);

    MyComponent myComponent = DaggerMyComponent.create();
    MembersInjector<MyClass> injector = myComponent.getInjector();
    injector.injectMembers(myClass);
    str = myClass.getStr();
    System.out.println("result = "+str);
}

의존성 주입하기

Dagger 에는 3가지 의존성 주입 방법을 제공한다

  • 필드 주입
  • 생성자 주입
  • 메서드 주입

@Inject가 붙은 필드, 메서드 또는 생성자에 인스턴스를 주입하는데 실무에서는 필드주입과 생성자 주입이 주로 사용된다.

@Component(modules = PersonModule.class)
public interface PersonComponent{
    PersonA getPersonA();//프로비전 메서드 
    void inject(PersonB personB); //멤버-인젝션 메서드 
}

@Module
public class PersonModule{

    @Provides
    String provideName(){
    	return "Charles";
    }

    @Provides
    int provideAge(){
    	return 100;
    }
}

public class PersonA{
    private String name;
    private int age;

    @Inject//생성자 주입
    public PersonA(String name,int age){
      this.name = name;
      this.age = age;
    }

    public String getName(){
    	return name;
    }

    public int getAge(){
    	return age;
    }
}

public class PersonB{
    @Inject//필드주입
    String name;

    private int age;

    @Inject//메서드 주입
    public void setAge(int age){
    	this.age = age;
    }

    public String getName(){
    	return name;
    }

    public int getAge(){
    	return age;
    }

이름과 나이를 제공하는 PersonModule을 PersonComponent에 추가하고 PersonA를 제공하는 프로비전 메서드와 PersonB를 제공하는 멤버-인젝션 메서드를 추가했다

@Test
public void testInjection(){
    PersonComponent personComponent = DaggerPersonComponent.create();

    PersonA personA = personComponent.getPersonA();
    System.out.println(personA.getName()+" :" +personA.getAge());

    PersonB personB = new PersonB();
    DaggerPersonComponent.create().inject(personB);
    assertEquals("Charles",personB.getName());

    assertEquals(100,personB.getAge());
}

상속된 클래스에 의존성 주입

멤버-인젝션 메서드를 호출할때 매개 변수 타입에 서브 클래스의 객체를 넣으면 해당 슈퍼 클래스의 @Inject 멤버만 의존성 주입이 이루어진다.

ex) 컴포넌트에 멤버-인젝션 메서드인 inject(self)가 존재하고 , child의 인스턴스를 멤버-인젝션 메서드의 매개 변수로 참조하여 메서드를 호출하면 child의 인스턴스에는 a,b만 주입되고 c에는 주입되지 않음을 알수 있다

public class Parent{
    @Inject
    A a;
}

public class Self extends Parent{
    @Inject
    B b;
}

public class Child extends Self{
    @Inject
    C c;
}

컴포넌트 객체 만들기

컴포넌트를 객체화 할때는 주로 생성된 빌더나 팩토리를 통해 만들 수 있다. 컴포넌트 내에 @Component.Builder 또는 @Component.Factory타입 선언을 통해 빌더 또는 팩토리가 생성되는데 둘다 없는경우 @Component 애노테이션에 선언된 모듈 및 의존성을 참조하여 빌더를 자동으로 생성한다.

다음과 같이 Builder를 직접 작성해야 한다면 다음과 같이 작성 가능하다

@Component(modules = MyModule.class)
public interface MyComponent{
...
    @Component.Builder
    interface Builder{
        Builder setModule(MyModule myModule);
        MyComponent build();
        }
    }
 }

컴포넌트 빌더를 만드는 조건

컴포넌트 빌더를 만드는 조건은 엄격하며 조건이 반드시 성립해야 한다 .

  • @Component.Builder 애노테이션은 컴포넌트 타입 내에 선언되어야 한다.
  • 반드시 매개변수를 갖지 않고, 컴포넌트 타입또는 컴포넌트의 슈퍼 타입을 반환하는 추상 메서드를 하나 포함해야 한다. 이를 빌드(Build method)메서드 라고 한다.
  • 빌드 메서드를 제외한 나머지는 세터 메서드라고 한다
  • @Component 애노테이션에 Modules, dependencies로 선언된 요소들은 세터 메서드로 선언해야 한다.
  • 세터 메서드는 반드시 하나의 매개변수만 가져야 하며 , 반환형으로는 void,Builder,빌더의 슈퍼타입이 될수 있다.
  • 세터 메서드에 @BindsInstance를 붙이면, 해당 컴포넌트에 인스턴스를 넘겨 바인드시킨다.
@Component(modules = {BackendModule.class,FrontendModule.class})
  interface MyComponent{
    MyWidget myWidget();

    @Component.Builder
    interface Builder{
        Builder backendModule(BackendModule bm);
        Builder frontendModule(FrondendModule fm);
        @BindInstance
        Builder foo(Foo foo);
        MyComponent build();
    }
}

컴포넌트 팩토리를 만드는 조건

  • @Component.Factory 애노테이션은 컴포넌트 타입내에 선언되어야 한다.
  • 컴포넌트 팩토리는 컴포넌트 타입 또는 컴포넌트의 슈퍼타입을 반환하는 하나의 추상 메서드만 존재해야 한다 (ex newInstance(..))
  • 팩토리 메서드에는 @Component 애노테이션에 modules,dependencies로 지정된 속성들을 반드시 매개변수로 가져야 한다.
  • 메서드에 @BindsInstance 애노테이션이 붙은 매개 변수는 해당 컴포넌트에 인스턴스를 넘겨 바인드시킨다.

생성되는 컴포넌트 타입에는 factory()라는 정적 메소드를 갖는데 팩토리 인스턴스를 반환한다. 이 팩토리 인스턴스를 초기화 할수 있다.

@Component(modules = {BackendModule.class,FrontendModule.class})
    interface MyComponent{
      MyWidget myWidget();

      @Component.Factory
      interface Factory{
          MyComponent newMyComponent(
          BackendModule bm,
          FrontendModule fm,
          @BindsInstance Foo foo);
    }
}

8.Lazy 주입과 Provider 주입

Lazy타입 또는 Provider 타입을 사용한다면 상황에 따라 의존성 주입의 시점을 늦추거나 새로운 객체를 요청할 수도 있다.

Lazy주입

객체가 초기화 되는데 시간이 필요하다면 Lazy주입을 고려해 볼 수 있다.
방법은 간단하다 바인드 된 타입 <T>을 제네릭을 갖는 Lazy<T>타입을 만들면 된다.
Lazy<T> 의 get()메서드를 호출하기까지는 객체가 초기화 되는것을 늦출 수 있다.

@Component(modules = CounterModule.class)
public interface CounterComponent{
	void inject(Counter counter);

///////
@Module
public class CounterModule{
    int next=100;

    @Provides
    Integer provideInteger(){
        System.out.println("computing...");
        return next++;
}
///////
public class Counter{
    @Inject
    Lazy<Integer> lazy;

    public void printLazy(){
        System.out.println("printing...");
        System.out.println(lazy.get());
        System.out.println(lazy.get());
        System.out.println(lazy.get());
     }
}
////////
@Test
public void testLazy(){
    CounterComponent component = CounterComponent.create();
    Counter counter = new Counter();
    component.inject(counter);
    counter.printLazy();
}

실행 결과
printing...
computing...
100
100
100

Provider 주입

매번 새로운 인스턴스를 주입받고 싶다면 Provider<T>를 고려해 볼수 있다.

public class Counter{

    @Inject
    Provider<Integer> provider;

    public void printProvider(){
        System.out.println("printing...");
        System.out.println(lazy.get());
        System.out.println(lazy.get());
        System.out.println(lazy.get());
    }
}

@Test
public void testProvider(){
    CounterComponent component = CounterComponent.create();
    Counter counter = new Counter();
    component.inject(counter);
    counter.printProvider();
}

실행결과
printing...
computing...
100
computing...
101
computing...
102

Provider.get()호출시 새로운 객체를 생성하기에 computing... 문구와 카운트가 1씩 증가하는 결과를 나타낸다.
만약 Component가 Singletone 과 같은 특정 범위로 지정되었다면 Provider<T> 를 사용한다고 하더라도 바인드된 의존성은 싱글턴으로 관리되어 같은 인스턴스를 제공받는다.

9. 한정자 지정하기

@Named 사용하기

때로는 반환형으로 바인드된 객체를 식별하기에 충분하지 않을 수 있다. 예를들어 하나의 컴포넌트에 바인드되어서 String을 반환하는 @Provides메서드가 두개이상인 경우 dagger에서 어느쪽으로 바인드할지 몰라 에러를 발생한다. 이를 방지하기 위해 나온것이 @Named 애노테이션의 멤버값에 식별가능한 문자열을 넣기만 하면 된다.

@Component(modules=MyModule.class)
public interface MyComponent{
	void inject(MyClass myclass)
}

@Module
public class MyModule{

    @Provides
    @Named("hello")
    String provideHello(){
        return "Hello";
    }

    @Provides
    @Named("World")
    String provideWorld(){
        return "World";
    }
}

public class MyClass{
    @Inject
    @Named("hello")
    String strHello;

    @Inject
    @Named("World")
    String strWorld;

    public String getStrHello(){
    	return strHello;
    }

    public String getStrWorld(){
	    return strWorld;
    }

}

@Test
public void myComponent(){
    MyComponent mycomponent = DaggerMyComponent.create();
    MyClass myclass = new MyClass();
    mycomponent.inject(myclass);
    System.out.println("strHello :"+myclass.getStrHello()+
    "strWorld:"+myclass.getStrWorld());
}

실행 결좌
Hello
World

사용자 정의 한정자 만들기

@Named 가 아닌 고유 한정자를 만들때 @Qualifier를 사용해 직접 한정자를 만들 수 있다.

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello{
}
////
@Component(modules = MyModule.class)
public interface MyComponent{
	void setMyClass(MyClass myClass)
}

/////
@Module
public class MyModule{

    @Provide
    @Hello
    Stirng provideHello(){
    	return "hello";
    }

    @Provide
    String provideWorld(){
    	return "world";
    }
}
/////
public class MyClass{

    @Inject
    @Hello
    Stirng strHello;

    @Inject
    String strWorld;

    String getStrHello(){
	    return strHello;
    }
    String getStrWorld(){
    	return strWorld;
    }
}
/////

@Test
public void myComponent(){
    MyComponent mycomponent = DaggerMyComponent.create();
    MyClass myclass = new MyClass();
    mycomponent.setMyClass(myclass);
    System.out.println(myclass.getStrHello() +" ~"+myclass.getStrWorld());
}

실행 결과
Hello ~ World

profile
💻디지털 노마드를 🚀꿈꾸는 🇯🇲자메즈 🐥개발자 입니다.

0개의 댓글