Рубрики
Без рубрики

Внедрение зависимостей в Java легко – Часть 2 – Использование с помощью Google Guice

Это серия постов, упрощающих и иллюстрирующих внедрение зависимостей в Java. С тегами java, программирование, учебник, код.

Часть 2. Использование возможностей Google Guice

Этот пост является частью учебника из нескольких частей, Эта часть, как следует из заголовка, будет посвящена использованию Инъекции зависимостей с помощью Google Guice .

Рекомендуется начать с Части 1 – Просто шаблона проектирования , чтобы иметь достаточное представление о шаблоне проектирования внедрения зависимостей , необходимом для этой части учебника.

Но, если вы хотите начать здесь вместо этого, я резюмирую в предложении: Внедрение зависимостей – это просто шаблон дизайна, техника для достижения Инверсия управления путем написания слабо связанного кода.

Вот и все, вы все пойманы в ловушку. 😁

Следующая часть этого руководства очень похожа на эту, в которой используется другая структура di : Часть 3 – Использование контекста Spring . Вы можете прочитать его вместо этого, если вы предпочитаете Весна .

Если ты все еще здесь, давай нырнем…

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

Ответ прост, Все … ❕

Я обещаю вам, что к тому времени, когда вы прочтете этот пост, вы поймете преимущества и необходимость включения di framework в ваше приложение.

Во-первых, давайте коснемся основы того, как работают фреймворки внедрения зависимостей … Это довольно прямолинейно, фреймворк создает зависимости для вас!

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

Это рамки di в двух словах, каждая структура DI, и я имею в виду Каждый фреймворк DI , с которым я сталкивался, в основном работает одинаково.

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

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

Например, вы можете найти сходства между:

Еще не верующий ❔ Хорошо, давайте добавим C# в микс ❕

Я могу продолжать… Но я верю, что моя точка зрения высказана. 😎

Под капотом framework создает фабрики, которые могут предоставлять зависимости объектам на основе нескольких критериев, таких как тип и имя.

На базовом уровне существуют три общие области жизненного цикла для зависимостей, живущих в контексте di framework :

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

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

  • Не Одноэлементный : экземпляр зависимости будет создан для каждого объявления при каждом объявлении , что означает, что каждый объявленный объект получит свой экземпляр зависимости.

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

Так …Давайте закодируем.

Предупреждаю, пример приложения такой же, как и в Части 1 – Просто шаблон проектирования за вычетом деталей перед включением шаблона проектирования внедрения зависимостей . Если вы получили раздел приложения из Часть 1 , вы можете перейти к разделу включение guice .

Вы можете ознакомиться с кодом для этой части руководства в Гитхаб .

1. Приложение для сбора Почты

Давайте создадим приложение, которое собирает почту как из Gmail , так и из Microsoft , используя Внедрение зависимостей шаблон с Google Guice .

1.1. Контракты

Перечисление вызывается Источник почты для классификации источника почты:

public enum MailSource {
  GMAIL,
  MICROSOFT;
}

Абстрактный класс Почта для заключения контрактов на почтовые объекты.

public abstract class Mail {
  public abstract String from();

  public abstract String subject();

  public abstract MailSource source();

  @Override
  public String toString() {
    return String.format("Got mail by %s, from %s, with the subject %s", source(), from(), subject());
  }
}

Интерфейс для контрактных служб, отвечающих за сбор почты от поставщиков, Почтовая служба .

public interface MailService {
  List getMail();
}

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

public interface MailEngine {
  List getAllMail();
}

1.2. Реализации

1.2.1. Почта

Для удобства и неизменности конкретные Почтовые реализации были разработаны с использованием шаблона builder . В Gmail Почта реализация, Mailimpl :

public final class GmailImpl extends Mail {
  private final String setFrom;
  private final String setSubject;

  private GmailImpl(final String from, final String subject) {
    setFrom = from;
    setSubject = subject;
  }

  @Override
  public String from() {
    return setFrom;
  }

  @Override
  public String subject() {
    return setSubject;
  }

  @Override
  public MailSource source() {
    return MailSource.GMAIL;
  }

  public static GmailImpl.Builder builder() {
    return new GmailImpl.Builder();
  }

  public static final class Builder {
    private String prepFrom;
    private String prepSubject;

    public Builder from(final String setFrom) {
      prepFrom = setFrom;
      return this;
    }

    public Builder subject(final String setSubject) {
      prepSubject = setSubject;
      return this;
    }

    public GmailImpl build() {
      requireNonNull(emptyToNull(prepFrom), "from cannot be empty or null");
      requireNonNull(emptyToNull(prepSubject), "subject cannot be empty or null");

      return new GmailImpl(prepFrom, prepSubject);
    }
  }
}

В Microsoft Почта реализация, Реализованная корпорацией Майкрософт :

public final class MicrosoftImpl extends Mail {
  private final String setFrom;
  private final String setSubject;

  private MicrosoftImpl(final String from, final String subject) {
    setFrom = from;
    setSubject = subject;
  }

  @Override
  public String from() {
    return setFrom;
  }

  @Override
  public String subject() {
    return setSubject;
  }

  @Override
  public MailSource source() {
    return MailSource.MICROSOFT;
  }

  public static MicrosoftImpl.Builder builder() {
    return new MicrosoftImpl.Builder();
  }

  public static final class Builder {
    private String prepFrom;
    private String prepSubject;

    public Builder from(final String setFrom) {
      prepFrom = setFrom;
      return this;
    }

    public Builder subject(final String setSubject) {
      prepSubject = setSubject;
      return this;
    }

    public MicrosoftImpl build() {
      requireNonNull(emptyToNull(prepFrom), "from cannot be empty or null");
      requireNonNull(emptyToNull(prepSubject), "subject cannot be empty or null");

      return new MicrosoftImpl(prepFrom, prepSubject);
    }
  }
}

1.2.2. Почтовые Услуги

В Gmail Почтовая служба внедрение:

public final class GmailService implements MailService {
  @Override
  public List getMail() {
    // this is where the actual Gmail api access goes.
    // we'll fake a couple of mails instead.
    var firstFakeMail =
        GmailImpl.builder()
            .from("a.cool.friend@gmail.com")
            .subject("wanna get together and write some code?")
            .build();

    var secondFakeMail =
        GmailImpl.builder()
            .from("an.annoying.salesman@some.company.com")
            .subject("wanna buy some stuff?")
            .build();

    return List.of(firstFakeMail, secondFakeMail);
  }
}

В Microsoft Почтовая служба внедрение:

public final class MicrosoftService implements MailService {
  @Override
  public List getMail() {
    // this is where the actual Microsoft api access goes.
    // we'll fake a couple of mails instead.
    var firstFakeMail =
        MicrosoftImpl.builder()
            .from("my.boss@work.info")
            .subject("stop writing tutorials and get back to work!")
            .build();

    var secondFakeMail =
        MicrosoftImpl.builder()
            .from("next.door.neighbor@kibutz.org")
            .subject("do you have philips screwdriver?")
            .build();

    return List.of(firstFakeMail, secondFakeMail);
  }
}

1.2.3. Почтовый Движок

Конкретная реализация Почтового движка является Надежным почтовым движком , он собирает почту из разных служб:

public final class RobustMailEngine implements MailEngine {
  private final Set mailServices;

  public RobustMailEngine(final Set setMailSerices) {
    mailServices = setMailSerices;
  }

  @Override
  public List getAllMail() {
    return mailServices.stream().map(MailService::getMail).flatMap(List::stream).collect(toList());
  }
}

1.3. Основное Приложение

Это само приложение, приложение Сборщик почты :

public final class MailCollectorApp {
  private MailEngine engine;

  public MailCollectorApp(final MailEngine setEngine) {
    engine = setEngine;
  }

  public String getMail() {
    var ret = "No mail found.";
    if (!engine.getAllMail().isEmpty()) {
      ret = Joiner.on(System.lineSeparator()).join(engine.getAllMail());
    }
    return ret;
  }

  public static void main(final String... args) {
    var gmailService = new GmailService();
    var microsoftService = new MicrosoftService();

    var engine = new RobustMailEngine(Set.of(gmailService, microsoftService));

    var app = new MailCollectorApp(engine);

    System.out.println(app.getMail());
  }
}

Выполнение основного метода приведет к печати:

Got mail by GMAIL, from a.cool.friend@gmail.com, with the subject wanna get together and write some code?
Got mail by GMAIL, from an.annoying.salesman@some.company.com, with the subject wanna buy some stuff?
Got mail by MICROSOFT, from my.boss@work.info, with the subject stop writing tutorials and get back to work!
Got mail by MICROSOFT, from next.door.neighbor@kibutz.org, with the subject do you have a star screwdriver?

Как вы, возможно, заметили, это приложение разработано с учетом шаблона проектирования инъекций зависимостей . Зависимости в настоящее время контролируются методом main , поэтому его должно быть легко включить Google Guice .

2. Включение интерфейса Google

2.1. Включить зависимость maven

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


  com.google.inject
  guice
  4.2.3

2.2. Пометить инъекцией

Нам нужно рассказать Guice о зависимостях, которые мы хотим ввести. Давайте сделаем это для конкретного класса двигателей:

public final class RobustMailEngine implements MailEngine {
  private final Set mailServices;

  @Inject
  public RobustMailEngine(final Set setMailSerices) {
    mailServices = setMailSerices;
  }

  @Override
  public List getAllMail() {
    return mailServices.stream().map(MailService::getMail).flatMap(List::stream).collect(toList());
  }
}

И для класса приложений:

public final class MailCollectorApp {
  private MailEngine engine;

  @Inject
  public MailCollectorApp(final MailEngine setEngine) {
    engine = setEngine;
  }

  public String getMail() {
    var ret = "No mail found.";
    if (!engine.getAllMail().isEmpty()) {
      ret = Joiner.on(System.lineSeparator()).join(engine.getAllMail());
    }
    return ret;
  }

  //...
}

Все, что мы сделали, добавив @Inject аннотация на конструкторе обоих классов, это сказать Хитрость нам это нужно, чтобы предоставить нам зависимости в этих конструкторах. Теперь нам нужно проинструктировать Руководство о том, как обеспечить эти зависимости.

2.3. Предоставлять зависимости

Давайте создадим Модуль Guice для настройки привязок существует несколько способов достижения этой цели. Например, использование Guice Предоставляет методы :

public final class DIModule extends AbstractModule {
  @Provides
  static Set getServices() {
    return Set.of(new GmailService(), new MicrosoftService());
  }

  @Provides
  @Singleton
  static MailEngine getEngine(final Set services) {
    return new RobustMailEngine(services);
  }
}

Другой вариант – использовать DSL привязки Guice :

public final class DIModule extends AbstractModule {
  @Override
  public void configure() {
    var listBinder = newSetBinder(binder(), MailService.class);
    listBinder.addBinding().toInstance(new GmailService());
    listBinder.addBinding().toInstance(new MicrosoftService());

    bind(MailEngine.class).to(RobustMailEngine.class).in(Scopes.SINGLETON);
  }
}

Оба варианта приведут к одинаковым зависимостям. Из Областей применения точка зрения… Область действия Guice по умолчанию Не является одноэлементной , означает Набор | Службы электронной почты и Служба Microsoft будет создана как новые экземпляры для каждого объекта, которому они нужны.

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

Этап по умолчанию для Хитрость является Стадией разработки , поэтому мы можем ожидать Ленивого синглтона .

Пожалуйста, обратите внимание, что Надежный почтовый движок является зависимостью, он будет введен тому, кому он нужен, но, как мы настроили ранее с помощью @Inject , ему также нужны зависимости для себя ( Набор из MailService ). Guice поймает это и введет набор почтовых служб при создании экземпляра движка.

2.4 Обновите приложение, чтобы использовать Guice

Давайте вернемся к нашему приложению и обновим его для работы с Guice :

public final class MailCollectorApp {
  private MailEngine engine;

  @Inject
  public MailCollectorApp(final MailEngine setEngine) {
    engine = setEngine;
  }

  public String getMail() {
    var ret = "No mail found.";
    if (!engine.getAllMail().isEmpty()) {
      ret = Joiner.on(System.lineSeparator()).join(engine.getAllMail());
    }
    return ret;
  }

  public static void main(final String... args) {
    var injector = Guice.createInjector(new DIModule());

    var app = injector.getInstance(MailCollectorApp.class);

    System.out.println(app.getMail());
  }
}

Давайте проанализируем, что здесь происходит…

Запуск метода main приведет к созданию Инжектор Guice , который является контекстом для всех зависимостей, в которых нужно жить. Поскольку мы создаем его с экземпляром нашего DI модуля класса, контекст инжектора будет иметь следующие зависимости, настроенные в нем:

  • Набор из двух Почтовая служба объекты ( Сервис Gmail и Служба Майкрософт
  • ). Одноэлементный экземпляр MailEngine ( (

Как уже говорилось, на Стадии разработки |Основной движок является Ленивым синглтоном , плюс мы настроили Набор | Почтовой службы как Не Одноэлементный . Это означает, что на данный момент в контексте инжектора ничего не создано. Все, что в нем есть, – это инструкции по созданию экземпляра.

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

  • Guice подберет конструктор в приложении Сборщик почты , так как это единственный конструктор.

  • Поскольку конструктор помечен @Inject , Guice будет искать в его контексте зависимость типа MailEngine .

  • Он найдет Надежный почтовый движок настроенный, который является Ленивым синглетоном .

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

  • Guice будет искать подходящую зависимость с типом Установить из Почтовой службы .

  • Он найдет Набор из Сервиса Google и Служба Microsoft , которая является Не одноэлементной .

После подготовки графика зависимостей , Guice будет:

  • Создайте набор после создания обоих экземпляров Служба Gmail и Служба Майкрософт .

  • Создайте экземпляр Надежного почтового движка , введя Набор .

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

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

Вот и все, Гайс в двух словах. 😆

Теперь давайте протестируем код…

3. Модульные тесты

Я начну с того, что скажу, что в отношении модульных тестов, если это возможно, всегда предпочитайте не использовать |/di-фреймворки в модульных тестах.

Модульные тесты – это тестирование небольших частей, блоков вашего приложения. Вам не нужны накладные расходы на создание контекста di , это, вероятно, не принесет вам никакой пользы. Вероятно, вам было бы лучше просто создать экземпляр тестируемого объекта вручную.

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

При этом, только для демонстрационных целей, давайте перейдем к модульным тестам с di framework .

public final class MailCollectorAppTest extends AbstractModule {
  private MailService gmailServiceMock;
  private MailService microsoftServiceMock;
  private MailService thirdServiceMock;

  private MailCollectorApp sut;

  private Faker faker;

  @Override
  public void configure() {
    var listBinder = newSetBinder(binder(), MailService.class);
    listBinder.addBinding().toInstance(gmailServiceMock);
    listBinder.addBinding().toInstance(microsoftServiceMock);
    listBinder.addBinding().toInstance(thirdServiceMock);

    bind(MailEngine.class).to(RobustMailEngine.class).in(Scopes.SINGLETON);
  }

  @BeforeEach
  public void initialize() {
    faker = new Faker();

    gmailServiceMock = mock(MailService.class);
    microsoftServiceMock = mock(MailService.class);
    thirdServiceMock = mock(MailService.class);

    var injector = Guice.createInjector(this);

    sut = injector.getInstance(MailCollectorApp.class);
  }

  @Test
  @DisplayName(
      "make the services mocks return no mail and validate the return string as 'No mail found'")
  public void getMail_noMailExists_returnsNoMailFound() {
    willReturn(emptyList()).given(gmailServiceMock).getMail();
    willReturn(emptyList()).given(microsoftServiceMock).getMail();
    willReturn(emptyList()).given(thirdServiceMock).getMail();

    then(sut.getMail()).isEqualTo("No mail found.");
  }

  @Test
  @DisplayName(
      "make the services return legitimate mail and validate the return string as expected")
  public void getMail_foundMail_returnsExpectedString() {
    var mail1 =
        GmailImpl.builder()
            .from(faker.internet().emailAddress())
            .subject(faker.lorem().sentence())
            .build();
    var mail2 =
        MicrosoftImpl.builder()
            .from(faker.internet().emailAddress())
            .subject(faker.lorem().sentence())
            .build();
    var mail3 =
        MicrosoftImpl.builder()
            .from(faker.internet().emailAddress())
            .subject(faker.lorem().sentence())
            .build();

    willReturn(List.of(mail1)).given(gmailServiceMock).getMail();
    willReturn(List.of(mail2, mail3)).given(microsoftServiceMock).getMail();
    willReturn(emptyList()).given(thirdServiceMock).getMail();

    then(sut.getMail().split(System.lineSeparator()))
        .containsOnly(mail1.toString(), mail2.toString(), mail3.toString());
  }
}

Как вы можете видеть, все, что я сделал, это создал инжектор с модулем, отличным от того, который я использовал для своего приложения ( DIModule ).

В этом случае я расширил тестовый класс, чтобы он сам по себе был модулем, и ввел свои насмешки вместо реальных Сервис Gmail и Служба Microsoft , я даже добавил третью службу, просто для пинка. 😁

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

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

Вы можете ознакомиться с кодом для этой части руководства в Гитхаб .

👋 Увидимся в следующей части этого урока 👋

Оригинал: “https://dev.to/tomerfi/dependency-injection-in-java-is-easy-part-2-leveraging-with-google-guice-6i4”