Contents

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.Injectjavax.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 将隐式注入一些通用组件,例如Injectorjava.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);
}