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 來真正的發(fā)送消息诽嘉。

添加 Maven 的依賴:

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

我們首先定義 Communicator 接口挺举,和它的一個實現(xiàn)類 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沐兰,傳入?yún)?shù) true 到構造方法
  • 它將 Communicator 綁定了到一個具體的實現(xiàn) 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)

假如我們現(xiàn)在有了 Communicator 接口的另外一種實現(xiàn) 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:一個接口和實現(xiàn)的綁定
  • 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> 包含了客戶代碼所依賴的類型以及一個可選的標注叁怪。你可以使用標注來區(qū)分指向同一類型的多個綁定审葬。

    • 例如,上述的代碼中奕谭,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 會幫你創(chuàng)建它的實例展箱。
    • 你也可以給 Guice 一個你要綁定的類的實例旨枯。
    • 你還可以實現(xiàn)你自己的 Provider<T>,Guice 可以向其中注入依賴關系混驰。
    • 例如攀隔,上述的代碼中,就有一個提供者是 class demo.guice.DefaultCommunicatorImpl
  • 每個綁定還有一個可選的作用域栖榨。缺省情況下綁定沒有作用域昆汹,Guice 為每一次注入創(chuàng)建一個新的對象。一個定制的作用域可以使你控制 Guice 是否創(chuàng)建新對象婴栽。例如满粗,你可以使用 為每一個 HttpSession 創(chuàng)建一個實例。

我們可以通過如下的方式遍歷每一個綁定 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); 的過程:
先根據(jù)指定的類來 new Key()愚争,Key 包括類信息 XXX.class 和注解信息映皆,XXX.classhashcode 和注解的 hashcode 決定了 Keyhashcode挤聘,getProvider 時是根據(jù) 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的區(qū)別

  • 使用方式:

    • Spring 將類與類之間的關系隔離到 XML 中步淹,由容器負責注入被調用的對象
    • Guice 不使用 XML从隆,而是使用注解 Annotation
  • 運行效率:

    • Guice 使用注解 Annotation,cglib, 效率高缭裆,這是與與 Spring 最明顯的一個區(qū)別键闺,Spring 是在裝載配置文件的時候把該注入的地方都注入完,而 Guice 呢澈驼,則是在使用的時候去注射艾杏,運行效率和靈活性高。
  • 類耦合度:

    • Spring 耦合度低盅藻,強調類非侵入购桑,以外部化的方式處理依賴關系,類里邊是很干凈的氏淑,在配置文件里做文章
    • Guice 耦合度高勃蜘,代碼級的標注,DI 標記 @inject 侵入代碼中假残,耦合到了類層面上來
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缭贡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辉懒,更是在濱河造成了極大的恐慌阳惹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眶俩,死亡現(xiàn)場離奇詭異莹汤,居然都是意外死亡,警方通過查閱死者的電腦和手機颠印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門纲岭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人线罕,你說我怎么就攤上這事止潮。” “怎么了钞楼?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵喇闸,是天一觀的道長。 經(jīng)常有香客問我,道長燃乍,這世上最難降的妖魔是什么唆樊? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮橘沥,結果婚禮上窗轩,老公的妹妹穿的比我還像新娘夯秃。我一直安慰自己座咆,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布仓洼。 她就那樣靜靜地躺著介陶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪色建。 梳的紋絲不亂的頭發(fā)上哺呜,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音箕戳,去河邊找鬼某残。 笑死,一個胖子當著我的面吹牛陵吸,可吹牛的內(nèi)容都是我干的玻墅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼壮虫,長吁一口氣:“原來是場噩夢啊……” “哼澳厢!你這毒婦竟也來了?” 一聲冷哼從身側響起囚似,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剩拢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饶唤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徐伐,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年募狂,在試婚紗的時候發(fā)現(xiàn)自己被綠了呵晨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡熬尺,死狀恐怖摸屠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粱哼,我是刑警寧澤季二,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響胯舷,放射性物質發(fā)生泄漏刻蚯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一桑嘶、第九天 我趴在偏房一處隱蔽的房頂上張望炊汹。 院中可真熱鬧,春花似錦逃顶、人聲如沸讨便。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸褒。三九已至,卻和暖如春盈蛮,著一層夾襖步出監(jiān)牢的瞬間废菱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工抖誉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留殊轴,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓袒炉,卻偏偏與公主長得像旁理,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子梳杏,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理韧拒,服務發(fā)現(xiàn),斷路器十性,智...
    卡卡羅2017閱讀 134,696評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,847評論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架叛溢,建立于...
    Hsinwong閱讀 22,435評論 1 92
  • 本來想吃完飯好好回答中午你問我的問題,還有微信里那篇你真的會談戀愛么劲适。 唉不知道該說什么好楷掉。你難受的時候我也渾身不...
    蒼洱皚皚白首不易閱讀 416評論 0 1
  • 論基本的、普世的智慧霞势,及其與投資管理和商業(yè)的關系 1994年4月14日烹植,南加州大學馬歇爾商學院 今天,我想對你們的...
    本格周閱讀 692評論 0 2