Google Guice 一个轻量级的依赖注入框架

Github 主页:https://github.com/google/guice
API:http://google.github.io/guice/api-docs/4.0/javadoc/

Guice (pronounced 'juice') is a lightweight dependency injection framework for Java 6 and above, brought to you by Google. 一个轻量级的依赖注入框架。

关于 Spring 的依赖注入,请参见 Spring 依赖注入 DI 的方式

一个 Google Guice 示例参见 Guide to Google Guice
例如我们有一个 Communication 类,它实际上是利用 Communicator 来真正的发送消息。

添加 Maven 的依赖:

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

我们首先定义 Communicator 接口,和它的一个实现类 DefaultCommunicatorImpl

public interface Communicator {
    boolean sendMessage(String message);
}
public class DefaultCommunicatorImpl implements Communicator {
    public boolean sendMessage(String message) {
        System.out.println("Sending Message + " + message);
        return true;
    }
}

随后我们通过 @Inject 注解来在 Communication 类中注入 Communicator 类的依赖:

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

import java.util.logging.Logger;

public class Communication {
    @Inject
    private Communicator communicator;

    public Communication(Boolean keepRecords) {
        if (keepRecords) {
            System.out.println("Message logging enabled");
        }
    }

    public boolean sendMessage(String message) {
        communicator.sendMessage(message);
        return true;
    }

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

        Communication comms = injector.getInstance(Communication.class);

        comms.sendMessage("hello world");
    }
}

main() 中,可以看到我们通过 Injector 得到了一个 Communication 实例,随后调用了 sendMessage() 方法。

那么 BasicModule 类又是怎么样的呢?

The Module is the basic unit of definition of bindings. 定义依赖绑定的基本单元。

  • 它需要继承 AbstractModule
  • 它将 Communication 绑定了到一个实例 Instance,传入参数 true 到构造方法
  • 它将 Communicator 绑定了到一个具体的实现 DefaultCommunicatorImpl
import com.google.inject.AbstractModule;

public class BasicModule extends AbstractModule {

    @Override
    protected void configure() {
        // 表明:当需要 Communicator 这个变量时,我们注入 DefaultCommunicatorImpl 的实例作为依赖
        bind(Communicator.class).to(DefaultCommunicatorImpl.class);

        bind(Communication.class)
                .toInstance(new Communication(true));
    }
}

运行输出如下:

Message logging enabled
Sending Message + hello world

可以看到,Guice 通过代码的形式来注入并管理依赖,而不是通过 XML 配置文件的形式,这个与 Spring 不太一样。

我们也可通过 @Provides 注解来在 BasicModule 中定义依赖:

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

    @Provides
    @Singleton
    public Communicator getCommunicator() {
        return new DefaultCommunicatorImpl();
    }
}

其中 @Singleton 注解表明这个依赖的 Scope 是单例,它是延时加载的 lazily initiated。

如果我们对一个依赖进行了多次绑定,例如:

@Provides
@Singleton
public Communicator getCommunicator() {
    return new DefaultCommunicatorImpl();
}

@Provides
@Singleton
public Communicator getCommunicatorOneMoreTime() {
    return new DefaultCommunicatorImpl();
}

运行时会抛出如下的异常:

1) A binding to demo.guice.Communicator was already configured at demo.guice.BasicModule.getCommunicator().
  at demo.guice.BasicModule.getCommunicator(BasicModule.java:17)

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:466)
    at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:155)
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:107)
    at com.google.inject.Guice.createInjector(Guice.java:96)
    at com.google.inject.Guice.createInjector(Guice.java:73)
    at com.google.inject.Guice.createInjector(Guice.java:62)

假如我们现在有了 Communicator 接口的另外一种实现 AnotherCommunicatorImpl

public class AnotherCommunicatorImpl implements Communicator {
    public boolean sendMessage(String message) {
        System.out.println("Another Sending Message + " + message);
        return true;
    }
}

同时我们在 Communication 类中需要同时依赖于原有的 DefaultCommunicatorImpl 和新定义的 AnotherCommunicatorImpl,例如:

public class Communication {

    @Inject
    private Communicator communicator;

    @Inject
    private Communicator anotherCommunicator;

    public Communication(Boolean keepRecords) {
        if (keepRecords) {
            System.out.println("Message logging enabled");
        }
    }

    public boolean sendMessage(String message) {
        communicator.sendMessage(message);

        anotherCommunicator.sendMessage(message);

        return true;
    }

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

        Communication comms = injector.getInstance(Communication.class);

        comms.sendMessage("hello world");
    }
}

那么我们在 BasicModule 应该怎么定义这种绑定呢?
如果我们尝试添加另外一个 @Provides 方法,返回 AnotherCommunicatorImpl,例如:

@Provides
@Singleton
public Communicator getCommunicator() {
    return new DefaultCommunicatorImpl();
}

@Provides
@Singleton
public Communicator getAnotherCommunicator() {
    return new AnotherCommunicatorImpl();
}

则会有如下的异常:

Exception in thread "main" com.google.inject.CreationException: Unable to create injector, see the following errors:

1) A binding to demo.guice.Communicator was already configured at demo.guice.BasicModule.getCommunicator().
  at demo.guice.BasicModule.getAnotherCommunicator(BasicModule.java:23)

这里我们需要通过 @Named 注解提供为属性赋值的功能。
首先在注入绑定的时候使用 @Named 注解:

@Inject
@Named("communicator")
private Communicator communicator;

@Inject
@Named("anotherCommunicator")
private Communicator anotherCommunicator;

随后在定义绑定的时候使用 @Named 注解:

@Provides
@Singleton
@Named("communicator")
public Communicator getCommunicator() {
    return new DefaultCommunicatorImpl();
}

@Provides
@Singleton
@Named("anotherCommunicator")
public Communicator getAnotherCommunicator() {
    return new AnotherCommunicatorImpl();
}

运行结果如下:

Message logging enabled
Sending Message + hello world
Another Sending Message + hello world

Guice 的工作原理

总的来说:

  • Guice:整个框架的门面
  • Injector:一个依赖的管理上下文
  • Binder:一个接口和实现的绑定
  • Module:一组 Binder
  • Provider:bean 的提供者
  • KeyBinder 中对应一个 Provider
  • ScopeProvider 的作用域

每个绑定 Binding<T> 的结构如下:

public interface Binding<T> extends Element {
    Key<T> getKey();

    Provider<T> getProvider();

同时它继承了 Element,里面包含了 Source:

public interface Element {
    Object getSource();

可以看出每个绑定 Binding<T>,包含一个键 Key<T> 和 一个提供者 Provider

  • Key<T> 唯一地确定每一个绑定。 Key<T> 包含了客户代码所依赖的类型以及一个可选的标注。你可以使用标注来区分指向同一类型的多个绑定。

    • 例如,上述的代码中,Communicator 类型的就有两个键:
    • Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=communicator)]
    • Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=anotherCommunicator)]
  • 对于每一个提供者 Provider,它提供所需类型的实例:

    • 你可以提供一个类,Guice 会帮你创建它的实例。
    • 你也可以给 Guice 一个你要绑定的类的实例。
    • 你还可以实现你自己的 Provider<T>,Guice 可以向其中注入依赖关系。
    • 例如,上述的代码中,就有一个提供者是 class demo.guice.DefaultCommunicatorImpl
  • 每个绑定还有一个可选的作用域。缺省情况下绑定没有作用域,Guice 为每一次注入创建一个新的对象。一个定制的作用域可以使你控制 Guice 是否创建新对象。例如,你可以使用 为每一个 HttpSession 创建一个实例。

我们可以通过如下的方式遍历每一个绑定 Binding<T>

Injector injector = Guice.createInjector(new BasicModule());

Map<Key<?>, Binding<?>> bindings = injector.getBindings();

for (Map.Entry<Key<?>, Binding<?>> bingingEntry : bindings.entrySet()) {

    Binding binging = bingingEntry.getValue();

    Key key =  binging.getKey();
    Provider provider = binging.getProvider();

    System.out.println("Key: " + key.toString());

    System.out.println("Provider: " + provider.get().getClass());

    System.out.println("************");
}

输出如下:

Key: Key[type=com.google.inject.Stage, annotation=[none]]
Provider: class com.google.inject.Stage
************
Key: Key[type=com.google.inject.Injector, annotation=[none]]
Provider: class com.google.inject.internal.InjectorImpl
************
Key: Key[type=java.util.logging.Logger, annotation=[none]]
Provider: class java.util.logging.Logger
************
Key: Key[type=demo.guice.Communication, annotation=[none]]
Provider: class demo.guice.Communication
************
Key: Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=communicator)]
Provider: class demo.guice.DefaultCommunicatorImpl
************
Key: Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=anotherCommunicator)]
Provider: class demo.guice.AnotherCommunicatorImpl
************

injector.getInstance(XXX.class); 的过程:
先根据指定的类来 new Key()Key 包括类信息 XXX.class 和注解信息,XXX.classhashcode 和注解的 hashcode 决定了 KeyhashcodegetProvider 时是根据 Keyhashcode 来判断是否是同一个Key,然后取到 Provider,由 Provider 提供最终的示例。
例如上面 Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=communicator)]Key[type=demo.guice.Communicator, annotation=@com.google.inject.name.Named(value=anotherCommunicator)]hashcode 就分别为 -1491509781349671560

Guice DI 与 Spring DI 的比较

参考 Guice与Spring的区别

  • 使用方式:

    • Spring 将类与类之间的关系隔离到 XML 中,由容器负责注入被调用的对象
    • Guice 不使用 XML,而是使用注解 Annotation
  • 运行效率:

    • Guice 使用注解 Annotation,cglib, 效率高,这是与与 Spring 最明显的一个区别,Spring 是在装载配置文件的时候把该注入的地方都注入完,而 Guice 呢,则是在使用的时候去注射,运行效率和灵活性高。
  • 类耦合度:

    • Spring 耦合度低,强调类非侵入,以外部化的方式处理依赖关系,类里边是很干净的,在配置文件里做文章
    • Guice 耦合度高,代码级的标注,DI 标记 @inject 侵入代码中,耦合到了类层面上来
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,510评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,680评论 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,229评论 1 92
  • 本来想吃完饭好好回答中午你问我的问题,还有微信里那篇你真的会谈恋爱么。 唉不知道该说什么好。你难受的时候我也浑身不...
    苍洱皑皑白首不易阅读 404评论 0 1
  • 论基本的、普世的智慧,及其与投资管理和商业的关系 1994年4月14日,南加州大学马歇尔商学院 今天,我想对你们的...
    本格周阅读 659评论 0 2