Google Guice 简介
1. 简介
本文将研究Google Guice 的基础知识。我们将研究在 Guice 中完成基本依赖注入 (DI) 任务的方法。 我们还将把 Guice 方法与更成熟的 DI 框架(如 Spring 和 Contexts and Dependency Injection (CDI))进行比较和对比。
本文假定读者了解依赖注入模式 的基本原理。
2. 设置
为了在您的 Maven 项目中使用 Google Guice,您需要将以下依赖项添加到您的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 和 IM。 考虑类:
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类是通信的基本单元。此类的一个实例用于通过可用的通信通道发送消息。如上所示,Communication有一个Communicator,我们用它来进行实际的消息传输。
Guice 的基本入口点是Injector:
public static void main(String[] args){
Injector injector = Guice.createInjector(new BasicModule());
Communication comms = injector.getInstance(Communication.class);
}
这个 main 方法检索我们的Communication类的一个实例。它还介绍了 Guice 的一个基本概念:Module (在此示例中使用BasicModule)。**Module 是定义绑定(或连线,在 Spring 中为人所知)的基本单元。
Guice 采用了代码优先的方法来进行依赖注入和管理,因此您无需处理大量开箱即用的 XML。
在上面的示例中,如果类具有默认的无参数构造函数,则Communication的依赖树将使用称为即时绑定的功能隐式注入。这从一开始就成为 Guice 的一个特性,并且仅在 v4.3 之后的 Spring 中可用。
3.2. Guice 绑定
绑定之于 Guice,就像连线之于 Spring。使用绑定,您可以定义 Guice 如何将依赖项注入到一个类中。 绑定在com.google.inject.AbstractModule的实现中定义:
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Communicator.class).to(DefaultCommunicatorImpl.class);
}
}
此模块实现指定在找到Communicator变量的任何地方都将注入DefaultCommunicatorImpl的实例。
这种机制的另一个体现是命名绑定。考虑以下变量声明:
@Inject @Named("DefaultCommunicator")
Communicator communicator;
为此,我们将有以下绑定定义:
@Override
protected void configure() {
bind(Communicator.class)
.annotatedWith(Names.named("DefaultCommunicator"))
.to(DefaultCommunicatorImpl.class);
}
此绑定将为使用*@Named(“DefaultCommunicator”)注解的变量提供Communicator*的实例。
您会注意到*@Inject和@Named*注解似乎是来自 Jakarta EE 的 CDI 的借用注解,它们确实是。它们位于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));
}
上面的代码片段将使用带有*boolean 参数的构造函数注入一个通信实例。我们通过定义Boolean *类的非目标绑定来为构造函数提供真正的参数。
这个非目标绑定将急切地提供给绑定中接受*boolean *参数的任何构造函数。使用这种方法,*Communication *的所有依赖项都被注入。
构造函数特定绑定的另一种方法是实例绑定,我们直接在绑定中提供一个实例:
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. 方法注入
这里我们使用一个setter方法来实现注入:
@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 支持我们在其他 DI 框架中已经习惯的范围和范围机制。Guice 默认提供已定义依赖项的新实例。
5.1. 单例
让我们在我们的应用程序中注入一个单例:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class).in(Scopes.SINGLETON);
in(Scopes.SINGLETON)指定任何带有@Named(“AnotherCommunicator”)的Communicator字段都将注入一个单例。这个单例默认是延迟启动的。
5.2. 饿汉式单例
现在,让我们注入一个饿汉式单例:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class)
.asEagerSingleton();
*asEagerSingleton()*调用将单例定义为急切实例化。
除了这两个范围之外,Guice 还支持自定义范围以及由 Jakarta EE 提供的仅限 Web 的*@RequestScoped和@SessionScoped*注解(这些注解没有 Guice 提供的版本)。
6. Guice 中的面向切面编程
Guice 符合 AOPAlliance 的面向方面编程规范。我们可以实现典型的日志拦截器,我们将在我们的示例中使用它来跟踪消息发送,只需四个步骤。
第 1 步 – 实现 AOPAlliance 的MethodInterceptor **:
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);
}