Google Guice指南

2025/04/25

1. 简介

在本教程中,我们将学习Google Guice的基础知识。然后,我们将介绍在Guice中完成基本依赖注入(DI)任务的一些方法。

我们还将比较Guice方法与更成熟的DI框架(如Spring和CDI)的方法。

本教程假设读者已经了解依赖注入模式的基础知识。

2. 设置

为了在我们的Maven项目中使用Google Guice,我们需要在pom.xml中添加以下依赖:

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>7.0.0</version>
</dependency>

这里还有一组Guice扩展(我们稍后会介绍),以及用于扩展Guice功能的第三方模块(主要是通过提供与更成熟的Java框架的集成)。

3. 使用Guice进行基本依赖注入

3.1 示例应用程序

我们将研究一种场景,设计支持帮助台业务中的三种通信方式的类:电子邮件、短信和即时通讯。

首先,让我们考虑一下这个类:

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的基本入口点是注入器:

public static void main(String[] args){
    Injector injector = Guice.createInjector(new BasicModule());
    Communication comms = injector.getInstance(Communication.class);
}

这个main方法获取了Communication类的一个实例,它还引入了Guice的一个基本概念:模块(本例中使用BasicModule),模块是定义绑定(或Spring中称为注入)的基本单元

Guice采用了代码优先的方法进行依赖注入和管理,因此我们不需要处理大量开箱即用的XML。

在上面的示例中,如果通信类具有默认的无参数构造函数,则将使用名为即时绑定(Just-in-Time Binding)的功能隐式注入依赖树。这是Guice自诞生以来就存在的功能,并且仅在Spring v4.3版本中可用。

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变量的任何地方注入Default CommunicatorImpl的实例。

3.3 命名绑定

此机制的另一个体现是命名绑定,考虑以下变量声明:

@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注解的支持。

3.4 构造函数绑定

我们还可以使用构造函数绑定注入没有默认无参数构造函数的依赖

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参数。

此外,此非目标绑定将被主动提供给绑定中任何接收布尔参数的构造函数,通过这种方式,我们可以注入Communication的所有依赖。

构造函数特定绑定的另一种方法是实例绑定,我们在绑定中直接提供一个实例:

public class BasicModule extends AbstractModule {
 
    @Override
    protected void configure() {
        bind(Communication.class)
            .toInstance(new Communication(true));
    }    
}

无论我们在何处声明通信变量,此绑定都会提供通信类的实例。

然而,在这种情况下,类的依赖关系树不会自动注入。此外,我们应该限制在不需要任何繁重初始化或依赖注入的情况下使用此模式。

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的面向切面编程规范,我们可以实现典型的日志拦截器,只需4步即可在我们的示例中使用它来跟踪消息发送。

第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);
}

7. 总结

了解了Guice的基本功能后,我们可以看出Guice的灵感来自Spring。

除了支持JSR-330之外,Guice还致力于成为一个以注入为中心的DI框架(而Spring为编程便利提供了整个生态系统,而不一定只是DI),面向需要DI灵活性的开发人员。

Guice还具有高度可扩展性,允许程序员编写可移植的插件,从而实现框架的灵活和创造性使用。此外,Guice还提供了对最流行的框架和平台(例如Servlet、JSF、JPA和OSGi)的广泛集成。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章