Как происходит работа с аннотациями в Dagger
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как происходит работа с аннотациями в Dagger
Dagger — это фреймворк для dependency injection (DI) на Java/Kotlin, который использует аннотации и code generation для создания graph зависимостей в compile-time. Это очень отличается от других DI фреймворков, которые работают через reflection в runtime.
Основной принцип работы
Dagger работает в три этапа:
- Compile-time: Аннотации обрабатываются annotation processor'ом
- Code generation: Генерируется Java код для создания объектов
- 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 фреймворков
| Feature | Dagger | Hilt | Koin |
|---|---|---|---|
| Compile-time | Да | Да | Нет |
| Reflection | Нет | Нет | Да |
| Code generation | Да | Да | Нет |
| Boilerplate | Много | Мало (Hilt = Dagger + автоматизация) | Минимум |
| Performance | Очень быстро | Очень быстро | Медленнее |
Hilt — это современный подход, это Dagger + Android обёртка для удаления boilerplate.