Статьи

Зависимость инъекций в GWT с использованием Dagger 2

Внедрение зависимостей — это концепция разработки программного обеспечения, в которой объектам предоставляются все объекты или значения, которые им необходимы для создания. Пользователи GWT были знакомы с GIN, но это последнее устарело и больше не поддерживается, поэтому приложениям, использующим GIN, в настоящее время действительно нужно прощаться. Dagger — это новая структура внедрения зависимостей для GWT. Для тех, кто не знаком с фреймворком, Dagger был нацелен на предоставление DI для Android, но теперь используется для DI общего назначения. Он также был адаптирован к GWT. В этой статье мы кратко расскажем о Dagger и о том, как настроить DI для проекта GWT с использованием Dagger.

Что в этом для GWT?

В отличие от GIN, который использует генераторы (которые будут удалены из GWT в будущем), Dagger использует процессоры аннотаций времени компиляции. Проекты, использующие Dagger, пройдут меньше проблем при обновлении версии GWT. С другой стороны, DI обычно представляет сложность, поэтому сложно отладить ошибки, возникающие во время внедрения. Известно, что трассировки стека GIN иногда не читаются. Одна из целей Dagger — уменьшить этот недостаток. Сгенерированный код Dagger близок к коду, написанному человеком, поэтому понимание того, что происходит внутри, может быть проще, и поэтому у разработчика будет меньше головной боли при отладке.

Использование Dagger в проекте GWT:

    1. зависимости
      01
      02
      03
      04
      05
      06
      07
      08
      09
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <dependency>
                  <groupId>javax.inject</groupId>
                  <artifactId>javax.inject</artifactId>
                  <version>1</version>
                  <scope>provided</scope>
              </dependency>
              <dependency>
                  <groupId>com.google.dagger</groupId>
                  <artifactId>dagger-gwt</artifactId>
                  <version>2.5</version>
                  <scope>provided</scope>
              </dependency>
              <dependency>
                  <groupId>com.google.dagger</groupId>
                  <artifactId>dagger-compiler</artifactId>
                  <version>2.5</version>
                  <scope>provided</scope>
              </dependency>

      Dagger требует, чтобы аннотации javax.inject находились в пути к классам при компиляции. Кроме того, модуль Dagger необходимо добавить в .gwt.xml:

      1
      2
      <inherits name="dagger.Dagger">
      </inherits>
    2. Процессор аннотаций

Если вы используете maven, вам нужно использовать версию плагина компилятора выше 3.5.1, если вы хотите, чтобы компилятор аннотаций автоматически выполнялся при вызове цели компиляции. В противном случае вам нужно будет указать и annotationProcessors, и annotationProcessorsPaths в конфигурации плагина. При желании компилятор dagger-compiler может быть удален из зависимостей и добавлен в annotationProcessorsPaths, как указано Томасом Бройером в SO :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.7</source>
        <target>1.7</target>
        <annotationProcessorPaths>
            <path>
                <groupId>com.google.dagger</groupId>
                <artifactId>dagger-compiler</artifactId>
                <version>${dagger.gwt.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Стоит также отметить, что в режиме dev процессор аннотаций необходимо перезапускать каждый раз, когда внедряемые классы меняются. В maven процессор аннотаций может быть запущен с использованием цели классов процессов. Я не тестировал Gradle, но концепции должны быть такими же, используя Gradle.

    1. Простое внедрение зависимости

Предположим, что у нас есть сервис, который сжимает изображения. Служба зависит от двух других служб: службы, которая загружает изображение, и службы, которая загружает изображение после сжатия. Все объекты имеют конструкторы с нулевым аргументом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class ImageCompressor {
     
    @Inject public ImageDownloader downloader;
    @Inject public ImageUploader uploader;
     
    @Inject
    public ImageCompressor(){  
    }
 
     
    public void compress(String url) {
        downloader.download(url);
        GWT.log("compressing image");
        uploader.upload(url);
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
public class ImageDownloader {
     
    @Inject
    public ImageDownloader() {
 
    }
 
    public void download(String url) {
        GWT.log("downloading image at " + url);
    }
     
     
}
01
02
03
04
05
06
07
08
09
10
11
12
public class ImageUploader {
     
    @Inject
    public ImageUploader() {
 
    }
 
    public void upload(String url) {
        GWT.log("uploading compresesed image at " + url);
    }
 
}
    1. Определение модуля

если вам нужны специальные настройки для создания объекта, такие как установка некоторых значений или указание аргументов конструктора, то вам нужно создать модуль. Предположим, что нам нужно предоставить значение тайм-аута для нашего объекта ImageDownloader:

01
02
03
04
05
06
07
08
09
10
11
12
public class ImageDownloader {
       int timeout;
     
    //@Inject we cannot use inject on the constructor anymore
    public ImageDownloader(int timeout) {
                 this.timeout = timeout;
    }
 
    public void download(String url) {
        GWT.log("downloading image at " + url);
    }  
}

Затем нам нужно указать модуль, который предоставляет наш ImageDownloader:

1
2
3
4
5
6
7
@Module
public class ImageCompressionModule {
 
    @Provides
    public ImageDownloader getImageDowloader(){
        return new ImageDownloader(15);
    }
    1. Определение компонента приложения

Теперь, когда мы определили наш модуль и объекты, мы создадим компонент DI, который будет использоваться для получения экземпляров внедренных объектов.

1
2
3
4
@Component(modules=ImageCompressionModule.class)
public interface AppComponent {
    ImageCompressor getImageCompressor();
}
    1. Использование введенных объектов

Экземпляр нашего компонента приложения можно получить следующим образом:

1
2
3
AppComponent component = DaggerAppComponent.builder()
               .imageCompressionModule(new ImageCompressionModule())
               .build();

Если вы используете IDE, вы заметите, что она жалуется на DaggerAppComponent. Это вполне нормально, поскольку DaggerAppComponent доступен только после запуска процессора аннотаций.

наконец, мы можем использовать наш объект:

1
2
ImageCompressor compressor = component.getImageCompressor();
       compressor.compress("http://www.g-widgets.com/GWTcon.jpg");

Результат:

1
2
3
downloading image at http://www.g-widgets.com/GWTcon.jpg
compressing image
uploading compressed image to http://www.g-widgets.com/GWTcon.jpg

Заворачивать:

Dagger 2 — это инъекция зависимостей следующего поколения для GWT. Мы увидели основные особенности фреймворка в этом посте. Более продвинутые функции DI можно найти в главном руководстве пользователя Dagger: https://google.github.io/dagger/users-guide . GWT-версия Dagger работает так же, как и бэкэнд-версия: код может работать как на стороне клиента, так и на стороне сервера, поэтому может быть полезно перенести DI на бэкэнд в случае возникновения проблем отладки в JVM.

Полный код доступен по адресу: https://github.com/zak905/dagger2-gwt-example