«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.