Данная статья является четвертой частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 10 декабря 2017 года. Перевод вольный.
Это четвертая статья цикла «Dagger 2 для начинающих Android разработчиков.». Если вы не читали предыдущие, то вам сюда.
Серия статей
- Dagger 2 для начинающих Android разработчиков. Введение.
- Dagger 2 для начинающих Android разработчиков. Внедрение зависимостей. Часть 1 .
- Dagger 2 для начинающих Android разработчиков. Внедрение зависимостей. Часть 2.
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Часть 1 (вы здесь).
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Часть 2.
- Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень.
Ранее в цикле статей
В последней статье мы поняли, что класс не должен создавать зависимости. Вместо этого, он должен получать их снаружи.
Также мы рассмотрели простой пример внедрения зависимостей в действии. Взяли пример с битвой бастардов и попытались избавиться от сильных связей (hard dependencies) через внедрение зависимостей.
Как внедрение зависимостей может усложниться?
Если проект такой же простой, как ранее рассмотренный пример, то создание экземпляров и внедрение небольшого количества зависимостей вручную, через точку входа (main()
или onCreate()
метод) в программу очень разумно. Однако, во многих проектах есть множество классов, у каждого из которых есть различные зависимости, которые нужно удовлетворить. Для создания экземпляров и подключения всего вместе требуется большое количество кода. Ещё хуже то, что этот код будет постоянно изменяться каждый раз при добавлении новых классов в приложение и при изменении существующих классов для того, чтобы внедрить новые зависимости.
Чтобы проиллюстрировать описанную проблему давайте немного усложним наш пример. Во время войны, в битве бастардов (BattleOfBastards
) вероятно потребуется помощь союзников (Allies
). Также железный банк (IronBank
) будет финансировать дома. Измененный главный метод будет выглядеть следующим образом:
public class BattleOfBastards {
public static void main(String[] args){
IronBank bank = new IronBank();
Allies allies = new Allies(bank);
Starks starks = new Starks(allies, bank);
Boltons boltons = new Boltons(allies, bank);
War war = new War(starks, boltons);
war.prepare();
war.report();
}
}
Очень быстро точка входа в приложение будет наполнена огромным количеством кода для инициализации всех зависимостей. Для создания одного класса, с которым мы будем работать, нужно инициализировать несколько других. По мере роста приложения и добавления в него новых классов, точка входа в приложение будет раздуваться и в конечном итоге этот метод станет очень сложно поддерживать.
Dagger 2 спешит на помощь
Dagger 2 — это один из фреймворков с открытым исходным кодом для внедрения зависимостей (далее буду использовать DI, от Dependency Injection), который генерирует большое количество шаблонного кода за вас. Почему он лучше остальных? Сейчас это единственный DI фреймворк, который генерирует полностью отслеживаемый Java код, имитирующий тот код, который вы могли написать вручную. Это означает, что в построении графа зависимостей нет никакой магии. Dagger 2 менее динамичен, чем другие (в нем не используется рефлексия), но простота и производительность сгенерированного кода находятся на том же уровне, что и у написанного вручную. Коротко, Dagger 2 генерирует весь шаблонный код для внедрения зависимостей за вас.
Ручное управление внедрением зависимостями — это как добыча драконьего стекла. Сначала вы получаете разрешение от королевы драконов, затем куете оружие и только потом идете воевать с Белыми Ходоками (проблемами сильных связей). Dagger 2 похож на валирийский меч — он был создан мастерами, и всё, что вам нужно — просто использовать его.
Понимание обработчиков аннотаций (Annotation Processors)
#Аннотации
Аннотации — это вид метаданных, который может быть связан с классами, методами, полями и даже другими аннотациями. Аннотации используются в Java для предоставления дополнительной информации, как альтернатива XML или маркерными интерфейсам (пустые интерфейсы). К аннотациям можно получить доступ и в процессе выполнения программы (runtime) через механизм рефлексии.
#Обработчики аннотаций (Annotation Processors)
Обработчики аннотаций — это генераторы кода, которые скрывают от вас шаблонный код, создавая его за вас во время компиляции. Пока эти действия выполняются во время компиляции, никакого отрицательного влияния на производительность нет.
#Почему я должен знать об обработчиках аннотаций?
Dagger 2 использует их. Таким образом, можно отследить весь сгенерированный код во время компиляции. Следовательно, нет ухудшения производительности, а ошибки легко отслеживаются.
#Примеры
Вы часто видите в классах аннотацию @Override
. Если вы использовали Butterknife, @BindView
— это тоже аннотация, которая скрывает за собой некоторые метаданные, помогающие генерировать код.
Аннотации Dagger 2
Рассмотрим некоторые аннотации Dagger 2 перед тем, как будем его использовать. Сейчас сосредоточимся на двух — @Inject
и @Component
.
@Inject
Это наиболее важная аннотация. JSR-330 определяет данную аннотацию как пометку для зависимостей, которые должны быть предоставлены фреймворком для внедрения зависимостей.
- Внедрение с использованием конструктора (Constructor Injection ) — используется с конструктором класса.
- Внедрение поля (Field Injection) — используется с полями класса.
- Внедрение с использованием метода (Method Injection ) — используется с методами
public class Starks{
/**
* Объяснение различного использования
* аннотации Inject в Dagger
**/
//Feild injection
@Inject
Allies allies;
//Constructor injection
@Inject
public Starks(){
// что-то происходит
}
//Method injection
@Inject
private void prepareForWar(){
// что-то происходит
}
}
Другими словами, аннотация @Inject
сообщит Dagger какие зависимости должны быть предоставлены зависимому объекту. Это как агенты железного банка, которые ведут переговоры с домами и определяют сумму кредита, которую могут предоставить дому.
@Component
Это аннотация используется для интерфейса, который объединит все части процесса внедрения зависимостей. При использовании данной аннотации мы определяем из каких модулей или других компонентов будут браться зависимости. Также здесь можно определить, какие зависимости будут видны открыто (могут быть внедрены) и где компонент может внедрять объекты. @Component
, в общем, что-то вроде моста между @Module
(рассмотрим эту аннотацию позже) и @Inject
.
Другими словами, эта аннотация как агент железного банка, который отвечает за утверждение кредита и перевод денег на соответствующий счет.
Убить Белых Ходоков валирийским мечом
Давайте используем Dagger 2 для примера с битвой бастардов. В этом примере нужны две зависимости для класса War
— Starks
и Boltons
.
Настройка Dagger 2
Для настройки Dagger 2 в InteliJ Idea используйте build.gradle
файл из ветки моего проекта. Также убедитесь в том, что обработка аннотаций подключена (File -> Settings -> Build, execution and deployment -> Compiler -> Annotation Processing -> Enable annotation processing (флаг должен быть установлен)). Не забудьте отметить и это: File -> Settings -> Build, execution and deployment -> Gradle -> Runner ->Delegate IDE build/run actions to cradle.
Добавление аннотации @Inject
План — внедрить зависимости Starks
и Boltons
в класс War
с помощью Dagger 2. О чём мы должны явно ему сказать. Ниже пример того, как это нужно делать, используя внедрение с использованием конструктора.
public class Boltons implements House {
@Inject //Dagger 2
public Boltons(){
}
@Override
public void prepareForWar() {
// что-то происходит
System.out.println(this.getClass().getSimpleName()+" prepared for war");
}
@Override
public void reportForWar() {
// что-то происходит
System.out.println(this.getClass().getSimpleName()+" reporting..");
}
}
public class Starks implements House {
@Inject //Dagger 2
public Starks(){
}
@Override
public void prepareForWar() {
// что-то происходит
System.out.println(this.getClass().getSimpleName()+" prepared for war");
}
@Override
public void reportForWar() {
// что-то происходит
System.out.println(this.getClass().getSimpleName()+" reporting..");
}
}
Эти две зависимости используются в конструкторе класса War
, где мы должны это отметить.
План заключается в том, чтобы сделать зависимость или объект класса War
доступным всем другим классам. Но для работы класса War
необходимо предоставить ему два класса, от которых он зависит — Starks
и Boltons
.
public class War {
private Starks
private Boltons boltons;
@Inject
public War(Starks starks, Boltons bolton){
this.starks = starks;
this.boltons = bolton;
}
public void prepare(){
starks.prepareForWar();
boltons.prepareForWar();
}
public void report(){
starks.reportForWar();
boltons.reportForWar();
}
}
Добавление аннотации @Component
Как мы узнали ранее, @Component
— это мост между генерируемым кодом и зависимостями. Также @Component
говорит Dagger 2 как необходимо внедрять зависимость. Сделаем интерфейс BattleComponent
внутри класса BattleOfBastards
(можно сделать и отдельно).
@Component
interface BattleComponent {
War getWar();
}
Этот интерфейс будет реализован классом, который сгенерирует Dagger 2, а функция getWar()
вернет экземпляр War
, который мы сможем использовать в подходящем месте.
Теперь необходимо пересобрать (rebuild) проект!
После сборки проекта вы увидите, что Dagger 2 сгенерировал класс под названием DaggerBattleComponent
— он поможет нам внедрить класс War
. Используем этот класс для получения экземпляра War
.
@Component
interface BattleComponent {
War getWar();
}
public class BattleOfBastards {
public static void main(String[] args){
// Ручное внедрение зависимостей
// Starks starks = new Starks();
// Boltons boltons = new Boltons();
// War war = new War(starks,boltons);
// war.prepare();
// war.report();
// Использование Dagger 2
BattleComponent component = DaggerBattleComponent.create();
War war = component.getWar();
war.prepare();
war.report();
}
}
Используя класс DaggerBattleComponent
мы можем использовать метод getWar()
, который возвращает экземпляр War
, который внедряет зависимости Starks
и Boltons
.
Поздравляю! Вы создали первый проект с использованием Dagger 2. Я очень ценю то, что вы нашли на это время и зашли так далеко. Время отпраздновать.
Резюме
Мы обсудили как ручное использование DI усложняет работу и увеличивает количество шаблонного кода. Далее обсудили как Dagger 2 помогает нам избавиться от этой боли и сам генерирует шаблонный код.
После разобрали информацию по обработчикам аннотаций и базовым аннотациям Dagger 2 (@Inject
и @Component
). Затем применили аннотации в нашем примере и внедрили зависимости, используя Dagger 2.
Что дальше?
В следующей статье мы поработаем с классами, которые генерирует Dagger 2 и рассмотрим другие аннотации этого фреймворка. Следующая статья выйдет через неделю.
Автор: Andrei