«1. Введение

В этом руководстве мы рассмотрим Dagger 2 — быстрый и легкий фреймворк внедрения зависимостей.

Фреймворк доступен как для Java, так и для Android, но высокая производительность, полученная за счет внедрения во время компиляции, делает его ведущим решением для последнего.

2. Внедрение зависимостей

Небольшое напоминание: внедрение зависимостей — это конкретное применение более общего принципа инверсии управления, в котором ход выполнения программы контролируется самой программой.

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

И разные фреймворки реализуют внедрение зависимостей по-разному. В частности, одно из наиболее заметных различий заключается в том, происходит ли внедрение во время выполнения или во время компиляции.

DI во время выполнения обычно основан на отражении, которое проще в использовании, но медленнее во время выполнения. Примером среды выполнения DI является Spring.

DI времени компиляции, с другой стороны, основан на генерации кода. Это означает, что все тяжелые операции выполняются во время компиляции. DI во время компиляции добавляет сложности, но обычно работает быстрее.

Кинжал 2 попадает в эту категорию.

3. Конфигурация Maven/Gradle

Чтобы использовать Dagger в проекте, нам нужно добавить зависимость от dagger в наш pom.xml:

<dependency>
    <groupId>com.google.dagger</groupId>
    <artifactId>dagger</artifactId>
    <version>2.16</version>
</dependency>

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

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.1</version>
    <configuration>
         <annotationProcessorPaths>
              <path>
                  <groupId>com.google.dagger</groupId>
                  <artifactId>dagger-compiler</artifactId>
                  <version>2.16</version>
              </path>
         </annotationProcessorPaths>
    </configuration>
</plugin>

С этой конфигурацией Maven будет выводить сгенерированный код в target/generated-sources/annotations.

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

В качестве альтернативы, если мы используем Android с Gradle, мы можем включить обе зависимости:

compile 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'

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

4. Реализация

В нашем примере мы попробуем построить автомобиль, вводя его компоненты.

Теперь Dagger во многих местах использует стандартные аннотации JSR-330, одним из которых является @Inject.

Мы можем добавить аннотации к полям или конструктору. Но, поскольку Dagger не поддерживает внедрение в приватные поля, мы воспользуемся внедрением в конструктор, чтобы сохранить инкапсуляцию:

public class Car {

    private Engine engine;
    private Brand brand;

    @Inject
    public Car(Engine engine, Brand brand) {
        this.engine = engine;
        this.brand = brand;
    }

    // getters and setters

}

Далее мы реализуем код для выполнения внедрения. В частности, мы создадим:

    модуль, который представляет собой класс, который предоставляет или создает зависимости объектов, и компонент, который представляет собой интерфейс, используемый для создания инжектора

Сложные проекты могут содержать несколько модулей и компоненты, но так как мы имеем дело с очень простой программой, достаточно одного из каждого.

Давайте посмотрим, как их реализовать.

4.1. Модуль

Чтобы создать модуль, нам нужно аннотировать класс аннотацией @Module. Эта аннотация указывает, что класс может делать зависимости доступными для контейнера:

@Module
public class VehiclesModule {
}

Затем нам нужно добавить аннотацию @Provides к методам, создающим наши зависимости:

@Module
public class VehiclesModule {
    @Provides
    public Engine provideEngine() {
        return new Engine();
    }

    @Provides
    @Singleton
    public Brand provideBrand() { 
        return new Brand("Baeldung"); 
    }
}

Также обратите внимание, что мы можем настроить объем заданной зависимости. В этом случае мы даем одноэлементную область видимости нашему экземпляру Brand, чтобы все экземпляры car использовали один и тот же объект brand.

4.2. Компонент

Двигаясь дальше, мы собираемся создать интерфейс нашего компонента. Это класс, который будет генерировать экземпляры Car, внедряя зависимости, предоставляемые VehiclesModule.

Проще говоря, нам нужна сигнатура метода, которая возвращает Car, и нам нужно пометить класс аннотацией @Component:

@Singleton
@Component(modules = VehiclesModule.class)
public interface VehiclesComponent {
    Car buildCar();
}

Обратите внимание, как мы передали класс нашего модуля в качестве аргумента аннотации @Component. Если бы мы этого не сделали, Dagger не знал бы, как построить зависимости автомобиля.

«Кроме того, поскольку наш модуль предоставляет одноэлементный объект, мы должны предоставить такую ​​же область действия нашему компоненту, потому что Dagger не позволяет компонентам без области действия ссылаться на привязки области действия.

4.3. Клиентский код

Наконец, мы можем запустить mvn compile, чтобы запустить обработчики аннотаций и сгенерировать код инжектора.

После этого мы найдем нашу реализацию компонента с тем же именем, что и у интерфейса, только с префиксом «Dagger»:

@Test
public void givenGeneratedComponent_whenBuildingCar_thenDependenciesInjected() {
    VehiclesComponent component = DaggerVehiclesComponent.create();

    Car carOne = component.buildCar();
    Car carTwo = component.buildCar();

    Assert.assertNotNull(carOne);
    Assert.assertNotNull(carTwo);
    Assert.assertNotNull(carOne.getEngine());
    Assert.assertNotNull(carTwo.getEngine());
    Assert.assertNotNull(carOne.getBrand());
    Assert.assertNotNull(carTwo.getBrand());
    Assert.assertNotEquals(carOne.getEngine(), carTwo.getEngine());
    Assert.assertEquals(carOne.getBrand(), carTwo.getBrand());
}

5. Аналогии со Spring

Те, кто знаком со Spring, возможно, заметили некоторые параллели между двумя фреймворками.

Аннотация @Module в Dagger сообщает контейнеру о классе очень похоже на любую из стереотипных аннотаций Spring (например, @Service, @Controller…). Аналогично, @Provides и @Component почти эквивалентны Spring @Bean и @Lookup соответственно.

Spring также имеет свою аннотацию @Scope, коррелирующую с @Singleton, хотя обратите внимание здесь уже на другое отличие в том, что Spring по умолчанию предполагает одноэлементную область, в то время как Dagger по умолчанию использует то, что разработчики Spring могут называть областью прототипа, вызывая метод провайдера. каждый раз, когда требуется зависимость.

6. Заключение

В этой статье мы рассмотрели, как настроить и использовать Dagger 2 на простом примере. Мы также рассмотрели различия между внедрением во время выполнения и во время компиляции.

Как всегда, весь код статьи доступен на GitHub.