1. введение
В этой статье мы рассмотрим основы Google Guice . Мы рассмотрим подходы к выполнению основных задач внедрения зависимостей (DI) в Guice.
Мы также сравним и сравним подход Guice с подходами более устоявшихся структур DI, таких как Spring и Contexts, а также внедрение зависимостей (CDI).
Эта статья предполагает, что читатель имеет представление об основах шаблона внедрения зависимостей .
2. Настройка
Чтобы использовать Google Guice в своем проекте Maven, вам нужно будет добавить следующую зависимость в свой pom.xml :
com.google.inject guice 4.1.0
Существует также коллекция расширений 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 является инжектор :
public static void main(String[] args){ Injector injector = Guice.createInjector(new BasicModule()); Communication comms = injector.getInstance(Communication.class); }
Этот основной метод извлекает экземпляр нашего класса Communication . Он также вводит фундаментальную концепцию сока: Модуль (используя Базовый модуль в этом примере). Модуль | является основной единицей определения привязок (или проводки, как это известно весной).
Guice принял подход, основанный на первом коде, для внедрения зависимостей и управления , поэтому вы не будете иметь дело с большим количеством XML-файлов из коробки.
В приведенном выше примере дерево зависимостей Communication будет неявно введено с помощью функции, называемой just-in-time binding , при условии, что классы имеют конструктор no-arg по умолчанию. Это была функция в Guice с момента ее создания и доступна только весной, начиная с версии v4.3.
3.2. Привязки Guice
Привязка к Guice так же, как проводка к пружине. С помощью привязок вы определяете, как Guice будет вводить зависимости в класс.
Привязка определяется в реализации com.google.inject.AbstractModule :
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communicator.class).to(DefaultCommunicatorImpl.class); } }
Эта реализация модуля указывает, что экземпляр Default Communicator Impl должен вводиться везде, где найдена переменная Communicator .
Другим воплощением этого механизма является именованная привязка . Рассмотрим следующее объявление переменной:
@Inject @Named("DefaultCommunicator") Communicator communicator;
Для этого у нас будет следующее определение привязки:
@Override protected void configure() { bind(Communicator.class) .annotatedWith(Names.named("DefaultCommunicator")) .to(DefaultCommunicatorImpl.class); }
Эта привязка предоставит экземпляр Communicator переменной, аннотированной аннотацией @Named(“Коммуникатор по умолчанию”) .
Вы заметите, что аннотации @Inject и @Named кажутся заимствованными аннотациями из CDI Jakarta EE, и это так. Они находятся в пакете com.google.inject.* — вы должны быть осторожны при импорте из правильного пакета при использовании IDE.
Совет: Хотя мы только что сказали использовать предоставленные Guice @Inject и @Named , стоит отметить, что Guice обеспечивает поддержку javax.inject.Inject и javax.inject.Named, среди других аннотаций Jakarta EE.
Вы также можете ввести зависимость, которая не имеет конструктора no-arg по умолчанию, используя привязку конструктора :
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|/.
Эта нецелевая привязка будет охотно предоставлена любому конструктору в привязке, который принимает логический параметр. При таком подходе вводятся все зависимости Communication .
Другим подходом к привязке к конкретному конструктору является привязка экземпляра , где мы предоставляем экземпляр непосредственно в привязке:
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communication.class) .toInstance(new Communication(true)); } }
Эта привязка обеспечит экземпляр класса Communication везде, где объявлена переменная Communication .
Однако в этом случае дерево зависимостей класса не будет автоматически подключено. Вы должны ограничить использование этого режима там, где нет необходимости в интенсивной инициализации или внедрении зависимостей.
4. Типы внедрения зависимостей
Guice поддерживает стандартные типы инъекций, которые вы ожидали бы от шаблона DI. В классе Communicator нам нужно ввести различные типы Режима связи .
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 будет неявно вводить некоторые компоненты общего назначения, такие как Инжектор и экземпляр java.util.Регистратор , среди прочих. Вы заметите, что мы используем регистраторы во всех образцах, но вы не найдете для них реальной привязки.
5. Определение области применения в Guice
Guice поддерживает области и механизмы преодоления, к которым мы привыкли в других системах DI. По умолчанию Guice предоставляет новый экземпляр определенной зависимости.
5.1. Синглтон
Давайте введем синглтон в наше приложение:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class).in(Scopes.SINGLETON);
in(Области видимости.SINGLETON) указывает, что в любое поле Communicator с именем @(“Другой коммуникатор”) будет введен синглтон. Этот синглтон лениво инициируется по умолчанию.
5.2. Нетерпеливый синглтон
Теперь давайте введем нетерпеливый синглтон:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class) .asEagerSingleton();
Вызов asEagerSingleton() определяет синглтон как нетерпеливый экземпляр.
В дополнение к этим двум областям Guice поддерживает пользовательские области, а также веб-аннотации @RequestScoped и @SessionScoped , поставляемые Jakarta EE (версии этих аннотаций, поставляемые Guice, отсутствуют).
6. Аспектно – ориентированное программирование в Guice
Guice соответствует спецификациям альянса AOP для аспектно-ориентированного программирования. Мы можем реализовать квинтэссенцию logginginterceptor, которую мы будем использовать для отслеживания отправки сообщений в нашем примере, всего за четыре шага.
Шаг 1 – Реализация метода-интерцептора 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 – Определите привязку для сопоставления:
Matches – это класс Guice, который мы используем, чтобы указать компоненты, к которым будет применяться наша аннотация AOP. В этом случае мы хотим, чтобы аннотация применялась к реализациям режима связи :
public class AOPModule extends AbstractModule { @Override protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(MessageSentLoggable.class), new 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 весной.
Наряду с поддержкой JSR-330 , Guice стремится стать ориентированной на инъекции платформой DI (в то время как Spring предоставляет целую экосистему для удобства программирования, а не только DI), ориентированную на разработчиков, которые хотят гибкости DI.
Guice также обладает высокой расширяемостью , позволяя программистам писать портативные плагины, которые обеспечивают гибкое и творческое использование фреймворка. Это в дополнение к обширной интеграции, которую Guice уже предоставляет для большинства популярных фреймворков и платформ, таких как сервлеты, JSF, JPA и OSGi, и это лишь некоторые из них.
Вы можете найти весь исходный код, используемый в этом руководстве, в нашем проекте GitHub .