作者:软件质量保障

之前发过一篇文章《​​浅谈依赖注入的实现​​》,介绍了依赖注入的实现原理。文中提到高效实现依赖注入的工具Guice,本文就介绍一下这款Google开源的依赖注入框架Guice及其使用方法。

1. 简介

Google Guice 是一个轻量级的依赖注入框架,它支持Java 5或者更高版本的JDK,得利于Java 5中提供的泛型 (Generics) 和注解 (Annotations) ,它可以使得代码类型安全 (type-safe) 。那么何时使用在代码中使用 Guice 进行注入呢?一般来说,如果在你的应用代码中业务对象 (Business Objects) 之间的关系或者依赖需要维护的话,你就可以使用Guice 进行注入。

本文会通过一些例子来初步的认识一下 Guice 框架。

当然,不了解依赖注入基础知识的同学建议先看下这篇科普贴 《​​浅谈依赖注入的实现​​》。

2. 添加依赖

将以下依赖项添加到Maven项目中放入pom.xml中:

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

3. Guice的基础用法

3.1 项目样例代码

我还是用上篇文章中那个例子作为演示场景,即以现实生活中的三种通信方式为例:Email、SMS 和 IM。

首先,我们定义Communication类:

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();
Communication comms = injector.getInstance(Communication.class)
comms.sendMessage("软件质量保障");
}

Guice 对依赖注入和管理采用代码优先的策略,因此我们可以不用处理很多令人抓狂的XML配置。

3.2. Guice bind

Binding is to Guice as wiring is to Spring。通过bind,我们可以实现Guice如何将依赖项注入到一个类中,

我们在com.google.inject.AbstractModule的实现中定义:

public class BasicModule extends AbstractModule {

@Override
protected void configure() {
bind(Communicator.class).to(DefaultCommunicatorImpl.class);
}
}

此模块实现将Communicator绑定到其默认实现类DefaultCommunicatorImpl上,在找到Communicator的地方都将注入Default CommunicatorImpl的实例。

3.3. @Named注解

我们可以使用 @Named注解来命名这些实体类,当你给它一个命名,它会返回一个命名好的 Annotation。例如在上面的例子中,可以使用 Names.named() 来完成相同的事情。

@Inject @Named("DefaultCommunicator")
Communicator communicator;
@Override
protected void configure() {
bind(Communicator.class)
.annotatedWith(Names.named("DefaultCommunicator"))
.to(DefaultCommunicatorImpl.class);
}

使用@Named(“DefaultCommunicator”)注解将Communicator绑定到DefaultCommunicator实现类。

3.4. 构造函数绑定

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

@Override
protected void configure() {
bind(Boolean.class).toInstance(true);
bind(Communication.class).toConstructor(
Communication.class.getConstructor(Boolean.TYPE));
}

构造函数绑定的另一种方法是实例绑定,我们直接为Communication.class绑定一个实例:

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. Method注入

这里我们将使用一个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的实例等。大家是不是发现了,我们的所有示例都使用了Logger,但你是不是找不到它的实际绑定代码。

5. Guice Scope机制

Guice支持我们在其他DI框架中逐渐习惯的Scope和Scope机制。

5.1 单例

下面在我们的应用程序中注入一个单例,我们指定了Communicator的Scope,它将会被标志为一个单例实例。

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class).in(Scopes.SINGLETON);

5.2. 饿汉式单例

下面注入一个饿汉式单例,asEagerSingleton()方法用来标记单例模式。

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class)
.asEagerSingleton();