Перейти к основному содержимому

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

· 7 мин. чтения

1. Введение

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

Мы также сравним подход Guice с подходами более известных DI-фреймворков, таких как Spring и Contexts and Dependency Injection (CDI).

В этой статье предполагается, что читатель понимает основы шаблона Dependency Injection .

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);
}

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

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

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

3.2. Привязки Гайса

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

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

public class BasicModule extends AbstractModule {

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

Реализация этого модуля указывает, что экземпляр Default CommunicatorImpl должен внедряться везде, где находится переменная 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 .

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

Шаг 1 — Реализуйте MethodInterceptor 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();
}
}

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

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

Шаг 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 .