1. Введение
В этом руководстве мы рассмотрим Dagger 2 — быструю и легкую среду внедрения зависимостей.
Платформа доступна как для Java, так и для Android, но высокая производительность, полученная за счет внедрения во время компиляции, делает ее ведущим решением для последней.
2. Внедрение зависимостей
Напоминаем, что внедрение зависимостей — это конкретное применение более общего принципа инверсии управления, в котором ход выполнения программы контролируется самой программой.
Он реализован через внешний компонент, который предоставляет экземпляры объектов (или зависимостей), необходимых другим объектам.
И разные фреймворки реализуют внедрение зависимостей по-разному. В частности, одно из наиболее заметных различий заключается в том, происходит ли внедрение во время выполнения или во время компиляции.
DI во время выполнения обычно основан на отражении, которое проще в использовании, но медленнее во время выполнения. Примером среды выполнения DI является Spring .
С другой стороны, DI во время компиляции основан на генерации кода. Это означает, что все тяжелые операции выполняются во время компиляции. DI во время компиляции добавляет сложности, но обычно работает быстрее.
Кинжал 2 попадает в эту категорию.
3. Конфигурация Maven/Gradle
Чтобы использовать кинжал в проекте, нам нужно добавить зависимость кинжала
в
наш 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("ForEach");
}
}
Также обратите внимание, что мы можем настроить область данной зависимости. В этом случае мы даем одноэлементную область видимости нашему экземпляру 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, возможно, заметили некоторые параллели между двумя фреймворками.
Аннотация Dagger @Module
делает контейнер осведомленным о классе очень похожим образом, как и любая из стереотипных аннотаций Spring (например, @Service
, @Controller
…). Аналогично, @Provides
и @Component
почти эквивалентны Spring @Bean
и @Lookup
соответственно.
Spring также имеет свою аннотацию @Scope
, коррелирующую с @Singleton
, хотя обратите внимание здесь уже на другое отличие в том, что Spring по умолчанию предполагает одноэлементную область, в то время как Dagger по умолчанию использует то, что разработчики Spring могут называть областью прототипа, вызывая метод провайдера каждый раз, когда требуется зависимость.
6. Заключение
В этой статье мы рассмотрели, как настроить и использовать Dagger 2 на простом примере. Мы также рассмотрели различия между внедрением во время выполнения и во время компиляции.
Как всегда, весь код в статье доступен на GitHub .