Ищите пример для инъекций с кинжалом

Из кинжал-обсудить @:

У меня есть класс, который получает некоторые зависимости от графа объектов и других зависимостей от вызывающего пользователя во время выполнения.

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

Нам не обязательно нужен специальный модуль для него, даже если это допустимая опция. Но у нас также могут быть эти аннотации в другом модуле, который уже установлен в компоненте. Приятно то, что нам нужно сделать это только один раз, и после этого любая фабрика автоматически станет частью графа.

При этом вы можете в основном внедрить фабрику и запросить свой объект, как обычно.