«1. Введение

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

Мы также сравним подход Guice с подходами более известных DI-фреймворков, таких как Spring и Contexts and 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 (мы рассмотрим их чуть позже) здесь, а также сторонние модули для расширения возможностей Guice (главным образом, обеспечивая интеграцию с более устоявшимися платформами Java).

3. Базовое внедрение зависимостей с помощью Guice

3.1. Наш пример приложения

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

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

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 есть коммуникатор, который мы используем для фактической передачи сообщений.

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

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

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

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

В приведенном выше примере дерево зависимостей связи будет неявно введено с использованием функции, называемой своевременной привязкой, при условии, что классы имеют конструктор без аргументов по умолчанию. Это было функцией Guice с самого начала и доступно только в Spring, начиная с версии 4.3.

3.2. Guice Bindings

Binding относится к 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(DefaultCommunicatorImpl.class);
}

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

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

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

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

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

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

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

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

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

«

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

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

4. Типы внедрения зависимостей

Guice поддерживает стандартные типы внедрения, которые вы ожидаете от шаблона DI. В классе Communicator нам нужно внедрить различные типы CommunicationMode.

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

4.1. Внедрение поля

Используйте необязательную аннотацию @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 поддерживает области действия и механизмы области видимости, к которым мы привыкли в других средах внедрения зависимостей. Guice по умолчанию предоставляет новый экземпляр определенной зависимости.

5.1. Синглтон

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

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

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

5.2. Нетерпеливый синглтон

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

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

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

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

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

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

public class MessageLogger implements MethodInterceptor {

    @Inject
    Logger logger;

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

Шаг 1 — Реализуйте метод Interceptor AOPAlliance:

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

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

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

public class AOPModule extends AbstractModule {

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

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

Шаг 4 — Примените нашу аннотацию к нашему режиму связи и загрузите наш модуль

@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 уже предоставляет для большинства популярных фреймворков и платформ, таких как сервлеты, JSF, JPA и OSGi, и это лишь некоторые из них.

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