Dagger 2
이전 글을 통해 Dagger를 이용한 의존성 주입(Dependency Injection) 구현법과 주요 Annotation 들을 간단히 살펴봤습니다.
@Inject
@Provides, @Module
@Component
이번에는 다른 Annotation들과 기타 Injections들을 살펴보겠습니다.
@Singleton
@Reusable
@CanReleaseReferences
@Qualifier
Lazy injections
Provider injections
먼저 Dagger에서 쓰이는 Scope의 개념부터 알아야 합니다.
Java에서 변수들은 사용 가능한 범위를 가지고, 그 범위를 변수의 Scope라고 합니다.
// Scope
public class ScopeClass {
int globalScopeVariables = 0;
void temp() {
int localScopeVariables = 1;
}
}
Dagger에서는 조금 의미가 다릅니다.
Dagger의 Scope는
해당 클래스의 단일(Single) 인스턴스가 존재하는 범위를 말합니다.
여기서 말하는 존재는 Garbage-collected가 발생할 때까지 Component 객체가 바인딩된 단일 인스턴스에 대한 참조를 보유한다는 의미입니다. 흔히 알고 있는 하나의 인스턴스만 만들어서 이용하는 싱글톤 패턴과 비슷한데, Application 전체가 아닌 범위도 지정할 수 있습니다.
예를 들면, Application scope는 Application의 생명주기만큼 오래 존재하고, Activity scope는 Activity가 존재하는 한 계속 존재할 수 있다는 의미입니다.
Application scope를 가지는 javax.inject.Singleton annotation입니다.
@Provides와 @Component에 @Singleton을 추가하면 됩니다.
Application Scope이기 때문에 Applicatino이 존재하는 한, 클래스의 단일 인스턴스도 존재하게 됩니다.
아래 코드는 동일한 인스턴스(의존성)를 주입하기 위해 @Provides와 @Component에 @Singleton을 추가한 코드입니다.
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class MediaModule {
@Provides @Singleton
public Video provideVideo() {
return new Video();
}
}
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {MediaModule.class})
public interface MediaComponent {
MediaController mediaController();
}
여기서 주의할 점이 있습니다.
Dagger 2 does not allow unscoped components to use modules with scoped bindings.
Unscoped components cannot have scoped dependencies.
Dagger 2에서는 서로 다른 Scope로 인해 문제가 일어날 수 있기 때문에 unscope Component는 scope Module을 가질 수 없습니다.
즉, Application scope인 @Singleton @Provides을 이용하기 위해서는 @Singleton @Component를 이용해야 합니다.
바인딩된 객체가 반환될 때 재사용될 수도 아닐 수도 있는 scope를 가지는 dagger.Reusable annotation입니다.
정의가 애매한데 @Singleton과 비슷합니다.
@Singleton
항상 동일한 인스턴스를 재사용.
@Component와 관련 있음.
@Reusable
새로운 인스턴스를 생성할 수 있지만, 이미 인스턴스가 존재한다면 재사용.
다른 scope와 달리 @Component와 관련 없음.
항상 동일한 인스턴스를 이용해야 하는 환경이 아니라면 메모리 할당 측면에서 더 유용합니다.
import dagger.Module;
import dagger.Provides;
import dagger.Reusable;
@Module
public class MediaModule {
// 각 @Component 별로 인스턴스를 새로 생성할 수 있기 때문에, immutable 객체에만 이용
@Provides @Reusable
public Video provideVideo() {
return new Video();
}
}
GC가 발생했을 때, dagger.CanReleaseReferences annotation을 이용하여 해당 인스턴스를 해제할 수 있습니다.
안드로이드와 같이 메모리가 중요한 환경에서는 메모리 부족 현상을 겪고 있을 때, GC가 발생하면 현재 이용되지 않는 범위가 지정된 객체를 삭제할 수 있습니다.
Java의 WeakReference와 비슷한 개념입니다.
자세한 내용은 아래 링크를 참조하시기 바랍니다.
때로는 타입만으로 의존성을 식별하기 어려운 경우, javax.inject.Qualifier annotation을 이용할 수 있습니다.
1. 우선 @Qualifier annotataion을 생성합니다.
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Qualifier;
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface VideoType {
String value() default "";
}
2. 의존성을 식별하기 위해 생성한 @Qualifier annotataion(@VideoType)을 추가합니다.
import javax.inject.Inject;
public class MediaController {
@Inject @VideoType("old") Video oldVideo;
@Inject @VideoType("new") Video newVideo;
@Inject MediaController() {}
public String getCurrentTime() {
int oldCurrentPosition = oldVideo.getCurrentPosition();
int newCurrentPosition = newVideo.getCurrentPosition();
}
}
3. 해당 @Provides 메서드에 @VideoType annotation을 추가해 규정된(qualified) 값을 제공합니다.
import dagger.Module;
import dagger.Provides;
@Module
public class VideoModule {
@Provides @VideoType("old")
public Video provideOldVideo() {
return new Video();
}
@Provides @VideoType("new")
public Video provideNewVideo() {
return new Video();
}
}
Dagger에서도 Lazy initialization 개념을 이용할 수 있습니다.
Lazy <T>를 이용해 객체가 생성될 때까지 초기화를 늦출 수 있습니다.
import javax.inject.Inject;
import dagger.Lazy;
public class MediaController {
Lazy<Video> video;
@Inject
MediaController(Lazy<Video> video) {
this.video = video;
}
public String getCurrentTime() {
// Video created once on first call to .get() and cached.
int currentPosition = video.get().getCurrentPosition();
return Integer.toString(currentPosition);
}
}
Provider <T>를 이용하여 매번 새로운 객체를 초기화할 수도 있습니다.
import javax.inject.Inject;
import javax.inject.Provider;
public class MediaController {
Provider<Video> video;
@Inject
MediaController(Provider<Video> video) {
this.video = video;
}
public String getCurrentTime() {
// new Video every time.
int currentPosition = video.get().getCurrentPosition();
return Integer.toString(currentPosition);
}
}
Direct injection, Provider injection and Lazy injection 이 3가지의 차이점은 아래 링크를 참조해 주시기 바랍니다.
https://google.github.io/dagger/users-guide.html
이제 조금 이해가 가네요.
단순하게 Dependency 요청하고, 주입해주는 framework인데 이를 도와주는 몇 가지 annotation들이 있는 구조네요.