Ищите пример для инъекций с кинжалом
Из кинжал-обсудить @:
У меня есть класс, который получает некоторые зависимости от графа объектов и других зависимостей от вызывающего пользователя во время выполнения.
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;
...
}
Я придумал решение, где я определяю Factory,
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
, поэтому пока не будем игнорировать эту возможность.
Люди Квадрата придумали интересное решение, используя поставщиков. Определите интерфейс 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);
}
}
...
}
(опять же, из кинжала-обсуждения @).
My ImageDownloader
- это класс, который вводится классом, который вводится другим классом, который вводится еще одним классом..., на который ссылается в @Module
. Это все как-то * работает, и все классы находятся во время сборки. Теперь, чтобы добавить модуль, я должен явно сообщить об этом объекту.
Мне что-то не хватает - очень легко ввести новый класс, но очень утомительно добавить новый модуль.
Мой вопрос: как сделать инъекцию на практике? У кого-нибудь есть пример? как я должен использовать ImageModule
, если вообще?
* - "как-то" действительно означает, что это отчасти волшебство для меня.
Ответы
Ответ 1
Итак, некоторые из игроков Dagger/Guice в Google создали вещь под названием AutoFactory (http://github.com/google/auto) в проекте, который включает AutoFactory (сгенерированный с помощью кода инъекции), AutoValue (генерируемые кодом настраиваемые типы значений) и AutoService (автогенерация файлов метаданных java-сервисов).
AutoFactory в значительной степени работает, как и следовало ожидать, - он генерирует factory, который в противном случае вы могли бы выполнить вручную. Это очень ранняя версия, и мы планируем гораздо большую гибкость, но она будет генерировать класс factory, который возьмет тип, который включает в себя некоторые зависимые от JSR-330 зависимости и некоторые параметры стека вызовов, и объединить их вместе при создании экземпляры аннотированного типа.
По сути, он сгенерирует factory, который вы написали, автоматически, если вы правильно комментируете свой factory -созданный тип.
Например, если вы создаете свой класс:
@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
}
}
AutoFactory будет генерировать:
@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, который вы можете вводить в свой график зависимостей (в кинжале или гике), и он будет создавать эти объекты для вас, совмещая состояние стека вызовов с предоставленные зависимости соответственно.
Вы можете ввести приведенное выше Just-In-Time, или вы можете предоставить его с помощью метода @Provides
на досуге.
Вы даже можете использовать factory интерфейс factory, а затем просто привязать их вместе в модуле кинжала так:
@AutoFactory(implementing = MyFactoryInterface.class)
public class ImageDownloader {
// ... otherwise as above...
}
@Module(...)
class MyModule {
@Provides MyFactoryInterface factoryImpl(ImageDownloaderFactory impl) {
return impl;
}
}
Ответ 2
Вы можете сделать вспомогательную инъекцию с помощью Dagger, используя квадрат/AssistedInject
Пожалуйста, проверьте также мой оригинальный ответ здесь: fooobar.com/questions/4899017/...
Ответ 3
Как сказал @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
Нам не обязательно нужен специальный модуль для него, даже если это допустимая опция. Но у нас также могут быть эти аннотации в другом модуле, который уже установлен в компоненте. Приятно то, что нам нужно сделать это только один раз, и после этого любая фабрика автоматически станет частью графа.
При этом вы можете в основном внедрить фабрику и запросить свой объект, как обычно.