Ищите пример инъекции с помощью кинжала
У меня есть класс, который получает некоторые зависимости от графа объекта и другие зависимости от вызывающего во время выполнения.
public class ImageDownloader {
// Get these dependencies from the injector.
private final HttpClient httpClient;
private final ExecutorService executorService;
// Get these from the caller.
private final URL imageUrl;
private final ImageCallback callback;
...
}
Я придумал решение, в котором я определяю Фабрику,
public class ImageDownloader {
...
public static class Factory {
private final HttpClient httpClient;
private final ExecutorService executorService;
@Inject
public Factory(HttpClient httpClient, ExecutorService executorService) {
this.httpclient = httpClient;
this.executorService = executorService;
}
public ImageDownloader create(URL imageUrl, ImageCallback callback) {
return new ImageDownloader(httpClient, executorService, iamgeUrl, callback);
}
}
...
}
Теперь вместо инъекций ImageDownloader
в конструкторе клиента я просто внедряю ImageDownloader.Factory
и назовите его create()
метод.
Как видите, это довольно многословно и долго. У этого также есть связка дублирования и шаблон. Есть некоторые препятствия для того, чтобы пометить сами поля @Inject
так что давайте пока проигнорируем эту возможность.
Люди Square придумали интересное решение, используя провайдеров. Определить Factory
интерфейс,
public class ImageDownloader {
...
public interface Factory {
ImageDownloader create(URL imageUrl, ImageCallback callback);
}
}
а затем предоставить его в модуле,
public class ImageModule {
...
@Provides
public ImageModule.Factory provideImageModuleFactory(
final Provider<HttpClient> httpClientProvider,
final Provider<ExecutorService> executorServiceProvider) {
return new ImageDownloader.Factory() {
public ImageDownloader create(URL imageUrl, ImageCallback callback) {
return new ImageDownloader(httpClientProvider.get(), executorServiceProvider.get(),
imageUrl, callback);
}
}
...
}
(опять же, из кинжала-обсуждения @).
мой ImageDownloader
это класс, который внедряется классом, который внедряется другим классом, который внедряется еще одним классом,..., на который ссылается в @Module
, Это все как-то * работает, и все классы находятся во время сборки. Теперь, чтобы добавить модуль, я должен явно сообщить об этом графу объектов.
Должно быть, я что-то упустил - очень просто добавить новый класс, но очень утомительно добавить новый модуль.
Мой вопрос: как на практике делается вспомогательная инъекция? У кого-нибудь есть пример? как я должен использовать ImageModule
, если вообще?
* - "как-то" действительно подразумевает, что это отчасти волшебно для меня.
4 ответа
Так, некоторые люди из Dagger/Guice в Google создали объект под названием AutoFactory ( http://github.com/google/auto) в проекте, который включает в себя AutoFactory (сгенерированный кодом ассистированный впрыск), AutoValue (сгенерированное кодом пользовательское значение типы) и AutoService (автоматическая генерация файлов метаданных java-сервисов).
AutoFactory в значительной степени работает так, как вы ожидаете, - он генерирует фабрику, которую вы бы иначе свернули вручную. Это очень ранняя версия, и у нас запланировано гораздо больше гибкости, но она сгенерирует фабричный класс, который примет тип, включающий некоторые инъекционные зависимости JSR-330 и некоторые параметры стека вызовов, и объединит их вместе при создании экземпляров аннотированный тип.
По сути, он автоматически сгенерирует фабрику, которую вы написали, если вы правильно аннотируете свой фабрично созданный тип.
Например, если вы создаете свой класс:
@AutoFactory
public class ImageDownloader {
// Get these dependencies from the injector.
private final HttpClient httpClient;
private final ExecutorService executorService;
// Get these from the caller.
private final URL imageUrl;
private final ImageCallback callback;
ImageDownloader(
@Provided HttpClient httpClient,
@Provided ExecutorService executorService,
ImageCallback callback,
URL imageUrl) {
// assignments
}
}
Автозавод будет генерировать:
@Generated("com.google.auto.factory.processor.AutoFactoryProcessor")
public final class ImageDownloaderFactory {
private final Provider<ExampleClasses.HttpClient> httpClientProvider;
private final Provider<java.util.concurrent.ExecutorService> executorServiceProvider;
@Inject
public ImageDownloaderFactory(
Provider<ExampleClasses.HttpClient> httpClientProvider,
Provider<java.util.concurrent.ExecutorService> executorServiceProvider) {
this.httpClientProvider = httpClientProvider;
this.executorServiceProvider = executorServiceProvider;
}
public ImageDownloader create(ImageCallback callback, URL imageUrl) {
return new ImageDownloader(
httpClientProvider.get(),
executorServiceProvider.get(),
callback,
imageUrl);
}
}
(Обратите внимание, у нас есть куча очистки для выходного источника, но вышеприведенное - это в основном то, что генерируется, хотя и не так хорошо отформатировано.)
Результирующий класс тогда, по сути, является JSR-330-совместимым инъецируемым классом, который вы можете внедрить в свой граф зависимостей (в Dagger или Guice), и он создаст эти объекты для вас, смешивая состояние стека вызовов с предоставленными зависимостями соответственно.
Вы можете ввести вышеупомянутый Just-In-Time, или вы можете предоставить его через @Provides
метод на досуге.
Вы можете даже заставить фабрику реализовать фабричный интерфейс, а затем просто связать их вместе в модуле кинжала следующим образом:
@AutoFactory(implementing = MyFactoryInterface.class)
public class ImageDownloader {
// ... otherwise as above...
}
@Module(...)
class MyModule {
@Provides MyFactoryInterface factoryImpl(ImageDownloaderFactory impl) {
return impl;
}
}
Как сказал @xsveda, для вспомогательной инъекции вы, вероятно, захотите использовать AssistedInject. Я написал об этом в этом посте, но я добавлю здесь полный пример, чтобы упростить процесс.
Первое, что вам нужно, это зависимости:
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
Тогда вот как будет выглядеть ваш пример:
class ImageDownloader @AssistedInject constructor(
private val httpClient: HttpClient,
private val executorService: ExecutorService,
@Assisted private val imageUrl: URL,
@Assisted private val callback: ImageCallback
) {
@AssistedInject.Factory
interface Factory {
fun create(imageUrl: URL, callback: ImageCallback): ImageDownloader
}
}
Во-первых, вместо того, чтобы аннотировать конструктор @Inject
мы аннотируем это @AssistedInject
, Затем мы аннотируем параметры, которые должны пройти фабрику, что противоположно тому, что ожидает AutoFactory. Наконец, нам нужен внутренний фабричный интерфейс с комментариями @AssistedInject.Factory
у него есть единственный метод, который получает вспомогательные параметры и возвращает интересующий нас экземпляр.
К сожалению, у нас все еще есть дополнительный шаг:
@AssistedModule
@Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule
Нам не обязательно нужен специальный модуль для него, даже если это допустимая опция. Но у нас также могут быть эти аннотации в другом модуле, который уже установлен в компоненте. Приятно то, что нам нужно сделать это только один раз, и после этого любая фабрика автоматически станет частью графа.
При этом вы можете в основном внедрить фабрику и запросить свой объект, как обычно.
Начиная с Dagger 2.31 от января 2021 года, Dagger теперь , что рекомендуется вместо параметров Square и Auto . Эти другие параметры по-прежнему работают, но могут потребовать дополнительной настройки по сравнению со стандартным вариантом.
В вашем случае вам нужно определить заводской интерфейс, используя@AssistedFactory
, а затем указать в конструкторе, какие аргументы исходят от него:
public class ImageDownloader {
@AssistedFactory
public interface Factory {
ImageDownloader create(URL imageUrl, ImageCallback callback);
}
@AssistedInject
public Factory(
HttpClient httpClient,
ExecutorService executorService,
@Assisted URL imageUrl,
@Assisted ImageCallback callback) {
// ...
}
}
Ваш модуль не должен быть задействован, и обратите внимание на отсутствие@Inject
в сравнении с@AssistedInject
. Как и в изначально поддерживает вспомогательную инъекциюдокументах , также связанных выше, есть варианты, когда два аргумента одного типа передаются через фабрику.
Вы можете сделать вспомогательную инъекцию с помощью Dagger, используя квадрат / AssistedInject
Пожалуйста, проверьте также мой оригинальный ответ здесь: /questions/39457597/mogu-li-ya-ispolzovat-inektsiyu-s-pomoschyu-dagger/39457609#39457609