← Назад к вопросам

Как происходит работа с аннотациями в Dagger

2.3 Middle🔥 211 комментариев
#Dependency Injection#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как происходит работа с аннотациями в Dagger

Dagger — это фреймворк для dependency injection (DI) на Java/Kotlin, который использует аннотации и code generation для создания graph зависимостей в compile-time. Это очень отличается от других DI фреймворков, которые работают через reflection в runtime.

Основной принцип работы

Dagger работает в три этапа:

  1. Compile-time: Аннотации обрабатываются annotation processor'ом
  2. Code generation: Генерируется Java код для создания объектов
  3. Runtime: Сгенерированный код выполняется без reflection

Ключевые аннотации Dagger

@Inject

Маркирует поле или конструктор, который Dagger должен инжектировать:

class UserRepository {
    @Inject
    lateinit var apiClient: ApiClient
}

// или в конструкторе (предпочтительно)
class UserRepository @Inject constructor(
    private val apiClient: ApiClient
)

Dagger анализирует все @Inject аннотации и определяет, какие зависимости нужны.

@Module

Класс, который содержит @Provides методы для создания объектов:

@Module
class NetworkModule {
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder().build()
    }
}

@Component

Интерфейс, который описывает entry point для инъекции и какие модули использовать:

@Component(modules = [NetworkModule::class, DataModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
    fun userRepository(): UserRepository
}

Dagger генерирует class DaggerAppComponent, который реализует этот интерфейс.

@Singleton

Аннотация scope, которая указывает, что объект должен быть создан один раз и переиспользован:

@Module
class AppModule {
    @Singleton
    @Provides
    fun provideDatabase(context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
    }
}

@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(app: MyApplication)
}

@Qualifier

Аннотация для различения нескольких объектов одного типа:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class BaseUrl

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class LoggingInterceptor

@Module
class NetworkModule {
    @Provides
    @BaseUrl
    fun provideBaseUrl(): String = "https://api.example.com"
    
    @Provides
    @LoggingInterceptor
    fun provideLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor()
}

Как это работает "под капотом"

Шаг 1: Annotation Processing

Во время компиляции Dagger's annotation processor:

  • Сканирует все @Inject, @Module, @Component аннотации
  • Строит dependency graph — граф зависимостей
  • Проверяет, что все зависимости могут быть разрешены
  • Если граф неполный — ошибка компиляции
// Вы пишете
class UserRepository @Inject constructor(
    private val apiClient: ApiClient
)

class ApiClient @Inject constructor(
    private val retrofit: Retrofit
)

// Dagger строит граф:
// UserRepository -> ApiClient -> Retrofit

Шаг 2: Code Generation

Dagger генерирует Java классы, которые создают объекты:

// Dagger генерирует это
public final class DaggerAppComponent implements AppComponent {
  private final Singleton_UserRepository userRepository;
  
  @Override
  public UserRepository userRepository() {
    return userRepository.get();
  }
  
  private static final class Singleton_UserRepository {
    private UserRepository instance;
    
    UserRepository get() {
      if (instance == null) {
        instance = new UserRepository(new ApiClient(new Retrofit(...)));
      }
      return instance;
    }
  }
}

Шаг 3: Runtime Usage

Вы просто вызываете сгенерированный код, никакой reflection:

// В Application
class MyApplication : Application() {
    lateinit var appComponent: AppComponent
    
    override fun onCreate() {
        super.onCreate()
        // Создаём component - вызывается сгенерированный код
        appComponent = DaggerAppComponent.builder().build()
    }
}

// В Activity
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var userRepository: UserRepository
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Инжектируем зависимости
        (application as MyApplication).appComponent.inject(this)
        // userRepository уже инициализирован
    }
}

Практический пример с полным циклом

// 1. Класс с @Inject конструктором
class UserRepository @Inject constructor(
    private val apiService: ApiService
) {
    fun getUsers() = apiService.fetchUsers()
}

// 2. Модуль для создания ApiService
@Module
class ApiModule {
    @Singleton
    @Provides
    fun provideApiService(): ApiService {
        return RetrofitClient.createService()
    }
}

// 3. Component
@Singleton
@Component(modules = [ApiModule::class])
interface AppComponent {
    fun userRepository(): UserRepository
    fun inject(activity: MainActivity)
}

// 4. Использование
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // DaggerAppComponent генерируется Dagger'ом
        val component = DaggerAppComponent.create()
        
        // Получаем объект с уже инжектированными зависимостями
        val repository = component.userRepository()
        repository.getUsers()
    }
}

Преимущества подхода Dagger

  • Compile-time safety: Все ошибки видны при компиляции, не в runtime
  • Zero reflection: Нет runtime overhead, работает очень быстро
  • Static graph: Весь граф известен на этапе компиляции
  • Type-safe: Полная типизация, все проверяется компилятором

Отличие от других DI фреймворков

FeatureDaggerHiltKoin
Compile-timeДаДаНет
ReflectionНетНетДа
Code generationДаДаНет
BoilerplateМногоМало (Hilt = Dagger + автоматизация)Минимум
PerformanceОчень быстроОчень быстроМедленнее

Hilt — это современный подход, это Dagger + Android обёртка для удаления boilerplate.