Dagger2 는 Dagger 로 용어를 통일하여 작성하였습니다.
① 안드로이드 앱 개발을 공부하다보니 의존성 주입에 대한 이야기를 많이 듣게됩니다. Android Dev Summit 2019 에서 "We believe that you should always apply dependency injection principles to your application" 라고 말할 정도로 앱 개발시 의존성 주입을 사용하는 것을 적극적으로 권장하는 것으로 보입니다. 이 카테고리에서는 여러 블로그에서 가장 많이 언급되는 Dagger 라는 의존성 주입 프레임워크에 대해 공부한 내용을 작성할 예정입니다. (처음 공부하는 것이라 틀린 내용이 포함될 수 있습니다. 틀린 내용은 댓글 남겨주시면 정말 감사드리겠습니다!)
② Dagger 는 어노테이션을 기반으로 빌드타임에 의존성 주입에 필요한 코드를 생성해냅니다. 이번 글에서는 Dagger 에서 생성한 코드를 분석하여, 기본적으로 Dagger 에서 어떤 방식으로 특정 클래스의 생성자 또는 필드에 객체를 주입하는지에 대해 확인해보았습니다.(사용한 예제는 흔히 보실 수 있는 Car와 Engine 클래스를 사용한 예제입니다.)
③ 공부에 사용한 예제는 아래와 같습니다.
-생성자를 통해 Engine 객체를 주입받을 Car 클래스
class Car @Inject constructor(private val engine: Engine) {
fun start() {
engine.start()
}
}
-Car 클래스에 주입될 Engine 클래스
class Engine @Inject constructor() {
fun start() {
Log.d("Dagger2Test", "Engine start")
}
}
-주입될 객체를 제공하는 역할을 하는 Module
@Module
class CarModule {
@Provides
fun provideCar(engine: Engine): Car {
return Car(engine)
}
@Provides
fun provideEngine(): Engine {
return Engine()
}
}
-Module 에서 주입될 객체를 제공받아 의존성 주입을 요청한 곳에 객체를 주입해주는 역할을 하는 Component
@Component(modules = [CarModule::class])
interface CarComponent {
fun inject(mainActivity: MainActivity)
}
-필드 주입을 받을 Activity 클래스
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent = DaggerCarComponent.create()
carComponent.inject(this)
car.start()
}
}
④ 3번의 예제들을 작성하고 빌드를 하면 \app\build\generated\source\kapt 폴더 아래에 Dagger 에서 생성한 코드들이 포함되어 있습니다. 이번 글에서는 해당 폴더 아래에 생성된 코드들을 분석하여 Dagger 에서는 어떤 방식으로 의존성 주입 기능을 제공하는지에 대해 살펴봅니다.
⑤ 먼저 예제의 MainActivity onCreate 안에서 DaggerCarComponent.create() 메서드를 호출하여 Component 객체 를 생성합니다.
@DaggerGenerated
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class DaggerCarComponent {
private DaggerCarComponent() {
}
public static Builder builder() {
return new Builder();
}
//1. Builder 클래스의 build 메서드를 호출합니다.
public static CarComponent create() {
return new Builder().build();
}
public static final class Builder {
private CarModule carModule;
private Builder() {
}
public Builder carModule(CarModule carModule) {
this.carModule = Preconditions.checkNotNull(carModule);
return this;
}
//2. Component 객체를 생성하기 전에 Component 와 연결된 Module 객체를 먼저 생성한 후
//Component 생성자를 호출합니다.
public CarComponent build() {
if (carModule == null) {
this.carModule = new CarModule();
}
return new CarComponentImpl(carModule);
}
}
private static final class CarComponentImpl implements CarComponent {
//4. Component 의 필드에 Module 이 포함되어있습니다.
private final CarModule carModule;
private final CarComponentImpl carComponentImpl = this;
//3. Component 의 생성자에서는 매개변수로 받은 Module 객체의 참조를 필드에 저장합니다.
private CarComponentImpl(CarModule carModuleParam) {
this.carModule = carModuleParam;
}
//...
}
}
⑥ Component 내의 의존성 주입을 시작하는 메서드(carComponent.inject(this@MainActivity))를 호출합니다.
이러한 형태의 메서드를 Dagger 에서는 멤버-인젝션 메서드라고 부릅니다. 멤버-인젝션 메서드는 의존성 주입을 받을 대상(예: MainActivity)을 매개변수로 받습니다. Dagger 를 처음 접한 입장에서 Component 내의 의존성 주입 메서드를 호출하면 실행되는 내용들이 조금 헤깔려서 보기 쉽게 순서대로 정리를 해보았습니다.
1) Component 인터페이스에 선언한 inject 메서드를 호출함으로써 의존성 주입이 시작됩니다. inject 메서드 내에서는 "Component에 선언한 메서드명 + 매개변수 타입"(예: injectMainActivity) 형태의 메서드를 호출합니다.
2) Dagger 에서 생성한 "모듈명_메서드명+Factory" 형태의 클래스에 선언된 static 메서드를 호출하고 최종적으로 Module 내의 메서드를 호출하여 주입될 객체를 생성합니다. 만약 주입될 객체(Car) 역시 의존성 주입을 받는다면(Engine) 먼저 생성합니다.
3) Dagger 에서 생성한 "매개변수타입_MembersInjector" 형태의 클래스에 선언된 static 메서드를 호출합니다. 이때 매개변수로 의존성 주입을 받을 대상(예: MainActivity)과 주입할 객체(Car)를 전달합니다. 이 static 메서드에서 실제로 의존성 주입을 받을 대상의 필드에 객체를 주입합니다.
@DaggerGenerated
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class DaggerCarComponent {
private DaggerCarComponent() {
}
public static Builder builder() {
return new Builder();
}
public static CarComponent create() {
return new Builder().build();
}
public static final class Builder {
private CarModule carModule;
private Builder() {
}
public Builder carModule(CarModule carModule) {
this.carModule = Preconditions.checkNotNull(carModule);
return this;
}
public CarComponent build() {
if (carModule == null) {
this.carModule = new CarModule();
}
return new CarComponentImpl(carModule);
}
}
private static final class CarComponentImpl implements CarComponent {
private final CarModule carModule;
private final CarComponentImpl carComponentImpl = this;
private CarComponentImpl(CarModule carModuleParam) {
this.carModule = carModuleParam;
}
//2. 의존성 주입을 하기 전에 Component 와 연결된 Module 을 통해 주입해야할 객체를 가져옵니다(예: CarModule_ProvideCarFactory.provideCar).
//주입해야할 객체(Car) 역시 의존성 주입을 받을 대상이라면, 다시 Module 을 통해 주입해야할 객체(Engine)을 가져옵니다.
//주입해야할 객체를 가져오는 역할은 Dagger 에서 생성해준 모듈명_메서드명Factory 클래스(예: CarModule_ProvideCarFactory) 내에 선언된
//Module 에 선언된 메서드와 이름이 동일한 static 메서드(예: provideCar, provideEngine)에서 합니다.
private Car car() {
return CarModule_ProvideCarFactory.provideCar(carModule, CarModule_ProvideEngineFactory.provideEngine(carModule));
}
//1. 의존성 주입을 시작합니다.
@Override
public void inject(MainActivity mainActivity) {
injectMainActivity(mainActivity);
}
private MainActivity injectMainActivity(MainActivity instance) {
//3. 실제로 의존성 주입을 받을 대상에 객체를 주입하는 메서드를 호출합니다.
MainActivity_MembersInjector.injectCar(instance, car());
return instance;
}
}
}
public final class CarModule_ProvideEngineFactory implements Factory<Engine> {
private final CarModule module;
public CarModule_ProvideEngineFactory(CarModule module) {
this.module = module;
}
@Override
public Engine get() {
return provideEngine(module);
}
public static CarModule_ProvideEngineFactory create(CarModule module) {
return new CarModule_ProvideEngineFactory(module);
}
//Module 의 메서드를 호출하여 주입할 객체를 가져옵니다.
public static Engine provideEngine(CarModule instance) {
return Preconditions.checkNotNullFromProvides(instance.provideEngine());
}
}
public final class CarModule_ProvideCarFactory implements Factory<Car> {
private final CarModule module;
private final Provider<Engine> engineProvider;
public CarModule_ProvideCarFactory(CarModule module, Provider<Engine> engineProvider) {
this.module = module;
this.engineProvider = engineProvider;
}
@Override
public Car get() {
return provideCar(module, engineProvider.get());
}
public static CarModule_ProvideCarFactory create(CarModule module,
Provider<Engine> engineProvider) {
return new CarModule_ProvideCarFactory(module, engineProvider);
}
//Module 의 메서드를 호출하여 주입할 객체를 가져옵니다.
public static Car provideCar(CarModule instance, Engine engine) {
return Preconditions.checkNotNullFromProvides(instance.provideCar(engine));
}
}
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
private final Provider<Car> carProvider;
public MainActivity_MembersInjector(Provider<Car> carProvider) {
this.carProvider = carProvider;
}
public static MembersInjector<MainActivity> create(Provider<Car> carProvider) {
return new MainActivity_MembersInjector(carProvider);
}
@Override
public void injectMembers(MainActivity instance) {
injectCar(instance, carProvider.get());
}
//MainActivity 의 @Inject 어노테이션이 선언되어있는 필드(lateinit var car: Car 필드) 에 Car 객체를 주입합니다.
@InjectedFieldSignature("com.antique_boss.dagger2car.MainActivity.car")
public static void injectCar(MainActivity instance, Car car) {
instance.car = car;
}
}
⑦ 이번 예제에서 사용한 의존성 주입 방식은 생성자 주입과 필드 주입입니다. 생성자 주입은 A 클래스의 객체를 생성할 때 B 클래스의 객체를 생성자의 매개변수로 넘겨주는 방식을 뜻하고, 필드 주입은 A 클래스의 필드에 B 클래스의 객체를 주입하는 방식을 뜻합니다. 예제에서는 생성자 주입의 예로 Car 객체를 생성할 때 매개변수로 Engine 객체를 생성하여 주입해주었고, 필드 주입의 예로 MainActivity 의 car 필드에 Car 객체를 주입해주었습니다. 다음 글에서는 생성자 주입과 필드 주입에 대해 작성할 예정입니다.
'dagger' 카테고리의 다른 글
Dagger2 with ViewModel (1) | 2022.12.29 |
---|---|
Dagger2 Singleton (0) | 2022.12.03 |
Dagger2 필드 주입 (0) | 2022.11.24 |
Dagger2 생성자 주입 (0) | 2022.11.23 |