【曹工雜談】Maven IOC 容器-- Guice內部有什么


Google Guice容器內部有什么

前言

Maven系列,好幾天沒寫了,主要是這幾天被Google Guice卡住了,本來是可以隨便帶過Guice,講講guice的用法就夠了(Maven容器的下半場:Guice,聽說僅次於Spring),但是,想着guice作為maven的底層IOC容器,對guice的理解深入一些,對后續的Maven源碼學習也會比較有幫助,因此,就在那開始分析guice的源碼。

guice作為一個僅次於Spring的IOC容器,代碼也不是那么好懂的,畢竟也迭代了十來年了;代碼量不少,另外,我感覺代碼也有點繞,就看得真心有點打瞌睡。

因為下班回來也9點多了,學習的時間也不多,因此,花了好幾天時間來單步debug,有一點點眉目,因此,這里先分享給大家,等后續理解深入了再補充。

針對Guice的源碼分析法

一般來說,我debug源碼,都是從頭開始,單步debug過去,很多時候,這種IOC框架啥的,啟動非常復雜,一個小時也跟不完一趟;過程冗長,一篇幾千字的文章基本都講不完,讀者也記不住那么多東西,博主也很難講清那么多東西。

我今天也想着換個思路吧,IOC容器,不是分兩個階段嗎,啟動時,一般是准備IOC容器;而運行時,就是去容器拿東西。根據我的發現,一般為了保證運行時足夠快,都會預先把數據准備好,比如,針對singleton類型的實例,都會預先生成(eager-initilization),存放到容器中,就無需運行時再去生成,歸根結底,就是一個空間換時間的方法。

采用這種空間換時間的方法,就會有個問題,就是在數據准備階段(比如容器初始化階段),要做的工作相當多,debug過程也非常長;甚至,有時候准備的很多數據,對於我們的場景,根本用不上。

因此,下面我會先給大家看看,初始化成功后的容器,是什么樣的;再去簡單分析背后的啟動過程。

簡單demo

一共三個類。


public interface HelloInterface {
    void hello();
}

public class HelloInterfaceImpl implements HelloInterface {
    @Override
    public void hello() {
        System.out.println("hello world");
    }
}

再下邊是啟動類:

這個啟動類,也就是三個部分:

  • 第一個部分,就是配置:HelloInterface這個class,要映射到 HelloInterfaceImpl這個實現類,后續,容器才能根據HelloInterface來new一個HelloInterface的實例出來。
  • 初始化容器
  • 運行時,從容器獲取HelloInterface的對象

容器中有什么

假設我們跳過初始化容器的階段,不關心容器如何構造,如何啟動,只看:構造好的容器,是什么樣的。

// 構造容器        
Injector injector = Guice.createInjector(module);

在執行完上面這句后,容器就已經初始化完畢,此時,我們打上斷點,看看容器的內部:

類型

真實類型是:

// Default Injector implementation.
final class InjectorImpl implements Injector, Lookups

從它實現的接口com.google.inject.Injector來看,主要有以下一些核心方法:

// 獲取當前容器內的全部綁定關系
Map<Key<?>, Binding<?>> getBindings();
// 根據key,獲取這個key對應的綁定關系。key其實基本就是一個接口的Class類名
<T> Binding<T> getBinding(Key<T> key);
// 根據class,獲取這個class對應的綁定
<T> Binding<T> getBinding(Class<T> type);

// 根據key,獲取對應的工廠類
<T> Provider<T> getProvider(Key<T> key);
// 根據class,獲取對應的工廠類
<T> Provider<T> getProvider(Class<T> type);

//根據key/class,直接獲取對應的實例
<T> T getInstance(Key<T> key);
<T> T getInstance(Class<T> type);

大家看到這里,是不是覺得和Spring的容器很像呢?

字段

  • 父容器

    final InjectorImpl parent;
    

    類似於spring,spring也有父子容器的概念;大體就是,當前容器找不到實例,還可以去父容器找

    我們這個demo里,parent是null

  • 綁定map

    final ListMultimap<TypeLiteral<?>, Binding<?>> bindingsMultimap;
    

    存儲了一些綁定關系,包括了三個默認的綁定,如:容器injector本身、日志logger、stage。

  • 容器選項

    final InjectorOptions options;
    

    這邊是一些配置項,比如jitdisabled,禁止隱式依賴。禁止后,你要向容器獲取Class X的實例,那么必須先配置X對應的實例化方式,不會再默認嘗試調用Class X的構造器(如果有的話)

  • 隱式綁定

    final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap();
    

    比如我們的這個實現類,就是個隱式綁定,因為我們沒配置如何實例化HelloInterfaceImpl。

  • 構造器緩存

      final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this);
    

    比如我們實現類的構造器,就被緩存了。

  • 內部狀態:state

    看了以上幾個字段,感覺也沒有很特別。其實,真正重要的字段,是下面將出場的這個。

    final State state;
    

    大家看下圖,會發現state下有不少字段,主要就有:每個class對應的綁定(value就是這個class的實例化方式)、還有我們代碼里配了個切面也在這里;基本上,這里才是真正的容器的各種數據的存放處

    接下來,我們再看看這個綁定關系的map。key就是對應的接口類,value就是說:怎么去實例化一個這個類型的實例出來,所以呢,guice內部,為了統一,基本把value這部分統一成了一個工廠。如下:

    而工廠類里是什么樣呢?

    就是包含了對應的實現類的構造器了。

    在真正要找容器獲取這個HelloInterface的實例時,就可以找到HelloInterfaceImpl的構造函數,從而構造一個實例出來。

不同的binding方式,內部不同的工廠類

當我們配置了一個如下的綁定關系時:

binder.bind(String.class).toInstance("xxx");

此時,內部又是什么樣呢?

這里,我們發現內部工廠internalFactory的類型,和之前也不太一樣了。同時,下圖可以看見,工廠內部直接存了這個String實例的值。

總之呢,也是保證后續直接就能在容器需要一個String類型實例時,找到“xxx”這個對象返回回去。

從容器中獲取

容器初始化好了,怎么獲取呢?即如下代碼怎么執行呢?

HelloInterface instance = injector.getInstance(HelloInterface.class);

我們稍微跟了下,發現就會走到如下地方,會去查詢state內部的顯示綁定map。

獲取到binding后,即取出internalFactory,然后構造/取出對象即可。

總結

不知道大家清晰一點了沒,希望對大家有幫助。后續會視情況,再看看是否分析構造容器的源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM