Dagger 2
Dagger is a fully static, compile-time dependency injection framework for both Java and Android.
Dagger는 자바와 안드로이드를 위한 의존성 주입 프레임워크입니다.
Dagger를 이용해 의존성 주입(Dependency Injection)을 어떻게 구현하는지 간단하게 살펴보겠습니다.
아래 코드는 이전 글에서 살펴본 의존성 주입 코드입니다.
단순히 Video 인스턴스 변수(의존성)를 MediaController 생성자를 통해 주입하는 코드입니다.
자바 코드만으로 의존성 주입을 쉽게 구현할 수 있지만, 만약 MediaController 클래스의 의존성이 변한다면, new MediaController 코드도 매번 수정해야 하는 문제가 있습니다.
public class MediaController {
Video video;
MediaController() { // Dependency Non-Injection
video = new Video();
}
MediaController(Video video) { // Dependency Injection
this.video = video;
}
public String getCurrentPlayTime() {
int currentPosition = video.getCurrentPosition();
return getPlayTime(currentPosition);
}
}
javax.inject.Inject annotation을 이용하면 Dagger에 의존성을 요청하기 때문에, 명시적으로 의존성을 주입시킬 필요가 없습니다.
따라서 MediaController 클래스의 의존성이 변해도 코드를 수정할 필요가 없습니다.
@Inject annotation을 통해 의존성을 생성자에 선언(Declaring)하면, 아래와 같이 Dagger를 통해 의존성을 주입하게 됩니다.
1. @Inject annotation 선언
2. new MediaController 인스턴스 요청
3. Dagger가 필수 parameter(Video video)를 가져와 MediaController 생성자 호출
import javax.inject.Inject;
public class MediaController {
Video video;
@Inject
MediaController(Video video) { // Dependency Injection with Inject annotation
this.video = video;
}
public String getCurrentTime() {
int currentPosition = video.getCurrentPosition();
return Integer.toString(currentPosition);
}
}
생성자 외에도 메서드와 필드에도 @Inject annotation을 선언할 수 있습니다.
자세한 내용은 아래 링크를 참조해 주시기 바랍니다.
http://docs.oracle.com/javaee/7/api/javax/inject/Inject.html
Dagger는 위에 언급한 대로, @Inject annotation 클래스의 인스턴스를 구성하여 각 의존성을 충족시킵니다. 이를 위해 아래 두 가지 annotation이 이용해야 합니다.
어떻게 의존성을 구성하고 제공하는지 정의하는 메서드에 이용합니다.
의존성을 제공하는 @Provides 메서드를 가진 클래스에 이용합니다.
모든 @Provides 메서드는 @Module 클래스 안에 속해야 합니다.
import dagger.Module;
import dagger.Provides;
// By convention, @Provides methods are named with a provide prefix and module
// classes are named with a Module suffix.
@Module
public class MediaModule {
@Provides
public Video provideVideo() {
return new Video();
}
}
@Inject와 @Module 사이의 의존성을 연결하기 위해서는 dagger.Component annotation을 이용해야 합니다.
@Component는 Interface에만 이용할 수 있으며, Component를 구성하는 모든 @Module 클래스 목록을 작성해야 합니다.
import dagger.Component;
@Component(modules = {MediaModule.class})
public interface MediaComponent {
MediaController mediaController();
}
@Component interface까지 만들면, Dagger는 이 interface를 implement 한 클래스를 자동으로 생성합니다. 생성된 클래스는 @Component interface 이름 앞에 "Dagger"를 붙이게 됩니다.
예를 들면, @Component interface MediaComponent {...}는 DaggerMediaComponent라는 이름을 가진 클래스를 생성합니다.
import dagger.internal.Preconditions;
import javax.annotation.Generated;
import javax.inject.Provider;
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://google.github.io/dagger"
)
public final class DaggerMediaComponent implements MediaComponent {
private Provider<Video> provideVideoProvider;
private Provider<MediaController> mediaControllerProvider;
private DaggerMediaComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static MediaComponent create() {
return builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideVideoProvider = VideoModule_ProvideVideoFactory.create(builder.videoModule);
this.mediaControllerProvider = MediaController_Factory.create(provideVideoProvider);
}
@Override
public MediaController mediaController() {
return new MediaController(provideVideoProvider.get());
}
public static final class Builder {
private VideoModule videoModule;
private Builder() {}
public MediaComponent build() {
if (videoModule == null) {
this.videoModule = new VideoModule();
}
return new DaggerMediaComponent(this);
}
public Builder videoModule(VideoModule videoModule) {
this.videoModule = Preconditions.checkNotNull(videoModule);
return this;
}
}
}
이제까지 했던 짓거리? 들은 궁극적으로 Dagger를 이용해 의존성 주입을 구현하고, MediaController instance를 얻어오기 위함이었습니다.
이를 위해 build()와 create() 두 가지 방법을 이용할 수 있습니다.
1. Dagger에 의해 생성된 DaggerMediaComponent 클래스의 builder() 메서드 호출
2. @Component를 구성하는 Module 인스턴스(new VideoModule()) 설정
3. build() 메서드 호출
4. MediaController 인스턴스 가져오기
// build
MediaComponent component = DaggerMediaComponent.builder()
.videoModule(new VideoModule())
.build();
MediaController controller= component.mediaController();
다른 Component와의 의존성이 없고, arg가 없는 default 생성자로 Module을 생성할 수 있으면 create() 메서드를 이용할 수 있습니다.
1. Dagger에 의해 생성된 DaggerMediaComponent 클래스의 create() 메서드 호출
2. MediaController 인스턴스 가져오기
// create
MediaComponent component = DaggerMediaComponent.create();
MediaController controller= component.mediaController();
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// create
MediaComponent component = DaggerMediaComponent.create();
MediaController controller= component.mediaController();
String currentTime = controller.getCurrentTime();
// build
component = DaggerMediaComponent.builder()
.videoModule(new VideoModule())
.build();
controller= component.mediaController();
currentTime = controller.getCurrentTime();
Log.d("Dagger", currentTime);
}
}
dependencies {
...
compile 'com.google.dagger:dagger:2.11-rc'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc'
provided 'javax.annotation:jsr250-api:1.0'
}
https://google.github.io/dagger/users-guide.html
Dagger 2 어렵습니다ㅠ
일단 이해한 부분만 작성했는데, 활용법은 이 외에도 무궁무진하네요.
저도 공부하면서 작성한거라, 혹시 틀린 부분이 있으면 댓글 달아주세요.
5월동안은 남은 몇가지 Dagger annotation과 안드로이드에서 좀 더 활용할 수 있는 방법에 대해 공부하고 글을 쓰려 합니다.