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 .