Руководство по Google Guice

1. Вступление

В этой статье будут рассмотрены основы Google Guice . Мы рассмотрим подходы к выполнению основных задач внедрения зависимостей (DI) в Guice.

Мы также сравним и сопоставим подход Guice с подходами более устоявшихся структур DI, таких как Spring и Contextx и Dependency Injection (CDI).

В этой статье предполагается, что читатель понимает основные принципы ссылки:/инверсия-контроль-и-зависимость-инъекция в пружине[шаблон внедрения зависимости].

2. Настроить

Чтобы использовать Google Guice в своем проекте Maven, вам нужно добавить следующую зависимость в свой pom.xml :

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.1.0</version>
</dependency>

Существует также коллекция расширений Guice (о них мы расскажем чуть позже) https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.google.inject.extensions%22% 20AND% 20v% 3A% 224.1.0% 22[здесь], а также as сторонние модули для расширения возможностей Guice (в основном за счет обеспечения интеграции к более устоявшимся фреймворкам Java).

3. Внедрение базовой зависимости с Guice

3.1. Наш образец приложения

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

Рассмотрим класс:

public class Communication {

    @Inject
    private Logger logger;

    @Inject
    private Communicator communicator;

    public Communication(Boolean keepRecords) {
        if (keepRecords) {
            System.out.println("Message logging enabled");
        }
    }

    public boolean sendMessage(String message) {
        return communicator.sendMessage(message);
    }

}

Этот класс Communication является базовой единицей связи. Экземпляр этого класса используется для отправки сообщений через доступные каналы связи. Как показано выше, у Communication есть Communicator , который мы используем для фактической передачи сообщений.

Основной точкой входа в Guice является Injector:

public static void main(String[]args){
    Injector injector = Guice.createInjector(new BasicModule());
    Communication comms = injector.getInstance(Communication.class);
}

Этот основной метод извлекает экземпляр нашего класса Communication . Он также вводит фундаментальную концепцию Guice: Module (с использованием BasicModule в этом примере). Module является основной единицей определения привязок (или проводки, как известно весной).

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

В приведенном выше примере дерево зависимостей Communication будет неявно внедрено с использованием функции just-in-time binding , при условии, что классы имеют конструктор по умолчанию без аргументов. Эта функция была в Guice с самого начала и доступна только весной начиная с v4.3.

3.2. Привязки Guice

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

Привязка определяется в реализации com.google.inject.AbstractModule :

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Communicator.class).to(DefaultCommunicatorImpl.class);
    }
}

Эта реализация модуля указывает, что экземпляр DefaultCommunicatorImpl должен вводиться везде, где находится переменная Communicator

  • Еще одним воплощением этого механизма является связывание с именем __. Рассмотрим следующее объявление переменной:

@Inject @Named("DefaultCommunicator")
Communicator communicator;

Для этого у нас будет следующее обязательное определение:

@Override
protected void configure() {
    bind(Communicator.class)
      .annotatedWith(Names.named("DefaultCommunicator"))
      .to(Communicator.class);
}

Эта привязка предоставит экземпляр Communicator для переменной, аннотированной аннотацией @ Named («DefaultCommunicator») .

Вы заметите, что аннотации @ Inject и @ Named выглядят как аннотации ссуд из CDI JavaEE, и это так. Они находятся в пакете com.google.inject. ** - при использовании IDE следует соблюдать осторожность при импорте из нужного пакета.

Совет: Хотя мы только что сказали использовать @ Inject и @ Named , предоставляемые Guice, стоит отметить, что Guice обеспечивает поддержку javax.inject.Inject и javax.inject.Named, среди других JavaEE аннотаций.

  • Вы также можете добавить зависимость, у которой нет конструктора по умолчанию без аргументов, используя constructor binding ** :

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Boolean.class).toInstance(true);
        bind(Communication.class).toConstructor(
          Communication.class.getConstructor(Boolean.TYPE));
}

Приведенный выше фрагмент кода внедрит экземпляр Communication , используя конструктор, который принимает аргумент boolean . Мы предоставляем аргумент true конструктору, определяя Нецелевое связывание класса Boolean .

Эта направленная привязка будет охотно предоставлена ​​любому конструктору в привязке, который принимает параметр boolean . При таком подходе вводятся все зависимости Communication .

  • Другой подход к привязке, специфичной для конструктора, - это instance binding ** , где мы предоставляем экземпляр непосредственно в привязке:

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Communication.class)
          .toInstance(new Communication(true));
    }
}

Эта привязка будет предоставлять экземпляр класса Communication везде, где объявлена ​​переменная Communication .

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

4. Типы инъекций зависимостей

Guice поддерживает стандартные типы инъекций, которые вы могли бы ожидать от паттерна DI. В классе Communicator нам нужно внедрить разные типы CommunicationMode .

4.1. Инъекция поля

@Inject @Named("SMSComms")
CommunicationMode smsComms;

Используйте необязательную аннотацию @ Named в качестве квалификатора для реализации целевого внедрения на основе имени

4.2. Метод инъекции

Здесь мы используем метод установки для достижения инъекции:

@Inject
public void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) {
    this.emailComms = emailComms;
}

4.3. Инъектор конструктора

Вы также можете ввести зависимости, используя конструктор:

@Inject
public Communication(@Named("IMComms") CommunicationMode imComms) {
    this.imComms= imComms;
}

4.4. Неявные инъекции

Guice неявно внедрит некоторые компоненты общего назначения, такие как Injector и экземпляр java.util.Logger , среди прочего Вы заметите, что мы используем регистраторы во всех примерах, но вы не найдете для них фактической привязки.

5. Обзор в Guice

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

5.1. Singleton

Давайте добавим синглтон в наше приложение:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
  .to(Communicator.class).in(Scopes.SINGLETON);

In (Scopes.SINGLETON) указывает, что любое поле Communicator с @ Named («AnotherCommunicator») будет вводиться синглтоном. Этот синглтон лениво инициируется по умолчанию.

5.2. Eager Singleton

Теперь давайте введем нетерпеливый синглтон:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
  .to(Communicator.class)
  .asEagerSingleton();

Вызов asEagerSingleton () определяет одноэлементный экземпляр как созданный с нетерпением.

В дополнение к этим двум областям, Guice поддерживает пользовательские области, а также аннотации @ RequestScoped и @ SessionScoped только для веб-сайтов, предоставляемые JavaEE (версии этих аннотаций, предоставляемые Guice, отсутствуют).

6. Аспектно-ориентированное программирование в Guice

Guice соответствует спецификациям AOPAlliance для аспектно-ориентированного программирования. Мы можем реализовать наиболее важный перехватчик ведения журнала, который мы будем использовать для отслеживания отправки сообщений в нашем примере, всего за четыре шага.

Шаг 1. Реализация AOPAlliance MethodInterceptor :

public class LoggingInterceptor implements MethodInterceptor {

    @Inject
    Logger logger;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[]objectArray = invocation.getArguments();
        int i = 0;
        for (Object object : objectArray) {
            logger.info("Sending message: " + object.toString());
        }
        return invocation.proceed();
    }
}

Шаг 2 - Определите простую Java-аннотацию :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageSentLoggable {
}

Шаг 3. Определите привязку для Matcher :

Matcher - это класс Guice, который мы используем и указываем компоненты, к которым будет применяться наша аннотация AOP. В этом случае мы хотим, чтобы аннотация применялась к реализациям CommunicationMode:

public class AOPModule extends AbstractModule {

    @Override
    protected void configure() {
        bindInterceptor(
            Matchers.any(),
            Matchers.annotatedWith(MessageSentLoggable.class),
            new MessageLogger()
        );
    }
}

Мы указали здесь Matcher , который будет применять наш перехватчик MessageLogger к классу any , к которому применена аннотация MessageSentLoggable к его методам.

Шаг 4 - примените нашу аннотацию к нашему CommunicationMode и загрузите наш модуль

@Override
@MessageSentLoggable
public boolean sendMessage(String message) {
    logger.info("SMS message sent");
    return true;
}

public static void main(String[]args) {
    Injector injector = Guice.createInjector(new BasicModule(), new AOPModule());
    Communication comms = injector.getInstance(Communication.class);
}

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

Посмотрев на базовую функциональность Guice, мы можем увидеть, откуда пришло вдохновение для Guice из Spring.

Наряду с поддержкой JSR-330 , Guice стремится стать средой DI, ориентированной на внедрение (тогда как Spring предоставляет целую экосистему для удобства программирования, а не только DI). ), нацелен на разработчиков, которые хотят гибкости DI.

Guice также обладает широкими возможностями расширения , что позволяет программистам создавать переносимые плагины, которые обеспечивают гибкое и творческое использование инфраструктуры. Это в дополнение к обширной интеграции, которую Guice уже предоставляет большинству популярных фреймворков и платформ, таких как Servlets, JSF, JPA и OSGi.

Вы можете найти весь исходный код, используемый в этом руководстве, в нашем GitHub проекте .