【曹工雜談】Maven IOC容器的下半場:Google Guice

三國夢迴發表於2021-09-14

Maven容器的下半場:Guice

前言

在前面的文章裡,Maven底層容器Plexus Container的前世今生,一代芳華終落幕,我們提到,在Plexus Container退任後,取而代之的底層容器是Guice。

Guice的應用也還比較廣泛,以下輪子中(僅部分)都有它活躍的身影:

  • google內部
  • scalatest
  • TestNG
  • Caffeine Cache
  • Spring Security Config
  • elastic search
  • jenkins

這很多輪子,都是直接用的Guice,那是因為沒什麼歷史包袱;但Maven不一樣,maven之前用自己的IOC輪子,有自己獨特的定義元件的方式(比如Spring通過@Component註解來定義);但是Guice作為一個獨立的IOC框架,那肯定是不認識Maven中的元件的。

因此,這中間肯定需要相容啊,Maven的元件,還是用的Plexus IOC容器那套方式去定義,但是他們開發了一箇中間層,把那些元件解析後,存放到了Guice容器中。

這裡說,把元件解析後,存放到了Guice容器中,這個也不是特別準確,更準確的說法是,放到了基於Guice進行了一層封裝的一個容器中,這個容器叫做:sisu,由eclipse在維護這個開源專案(https://www.eclipse.org/sisu/)。

可能你就疑惑了,就一個破IOC,搞得多有技術含量一樣,還一層套一層。。這個我們就先不管了,這期我先講Guice,然後大家就懂了,為啥Sisu要要封裝一層了。總結一下,依賴路徑是:

最底層的是google Guice --》 sisu(eclipse)--》 sisu-plexus相容層--》plexus --》maven。

好了,開始正文。

Guice是什麼

根據wiki的描述,Guice就是依賴注入框架,由google開源。主要特點就是:支援以java註解的方式配置元件及依賴。最早的版本應該是在2007年,我還找到一篇當年的報導 https://developers.googleblog.com/2007/03/,

當時還是2007年,而2004年的時候,支援註解的JDK1.5才放出來,與此同時,Spring早期版本主要也還是以xml配置為主,Guice在那時就支援全註解配置,以當時的眼光來看,很前沿了。

接下來,我們就來仔細瞭解下Guice的用法。

核心理念

在開始講之前,我說下我對IOC框架的理解先。很多時候,可以簡單地說,IOC容器是一個map,一個放東西的地方,就像一箇中藥房,每個格子裡會放一種藥材,而每個格子上,有一個標籤,來說明裡面放的是什麼藥材。

既然是放東西的地方,核心就是兩個部分,怎麼放,放的時候,可能就要考慮到後續怎麼找的問題。比如,如果你打算只支援根據物品的型別來找,那你要考慮到:如果這個型別的物品有多個,要怎麼辦?怎麼區分這多個物品?

如果你打算解決這個問題,那你可能就會想:那我放的時候,再給這些物品取個名字吧,免得多個相同型別的物品,分不清。

甚至呢,你可能會考慮,物品的許可權隔離,比如,物品上再加個存放人的欄位,以後取得時候,就只能:自己取自己的,不能取別人的。

反正,最終還是看需求,一般來說,像我們spring這種,按型別就差不多了,一個型別多個實現的時候,再根據名字區分一下就ok。

而Guice呢,我這邊會重點講解:怎麼存。至於取,可能還分成兩種,依賴注入和直接從容器中取。但是依賴注入的底層實現,也是:發現我依賴的某個東西沒有,就去容器裡取。

所以,取東西,我們只需要關注:直接從容器中怎麼獲取就行;我這邊就不會特別關注依賴注入的問題。

Guice中,存東西的多種方式

概覽

存東西,在Guice的文件裡,名詞叫做Binding,中文就是繫結吧。

https://github.com/google/guice/wiki/Bindings

繫結是什麼意思,就是我最終可能需要從容器中獲取ClassA型別的物件。既然要取,那還得先存,存的時候,我就要建立繫結:ClassA --》該Class型別物件的獲取方式。

其實還是很簡單的。下邊就跟著我的程式碼例子,我們來看看。

以下全部的程式碼,都在:

https://gitee.com/ckl111/maven-3.8.1-source-learn/tree/master/my-test-modules/official-guice-demo

1. linkedbinding-繫結介面到實現類

先來一個介面和實現:

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

再來看看,怎麼放到容器,和簡單的從容器中取出來的方法:

大家看我程式碼截圖,放東西的時候,就是要指定這個介面,對應的實現類。

取東西的時候,再去根據介面取就行了。

2.BindingAnnotations 一個型別有多個實現類的時候的繫結方式

介面和多個實現類:

interface HelloInterface {
    void hello();
}
class HelloInterfaceImpl implements HelloInterface {

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

class HelloInterfaceImpl2 implements HelloInterface {

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

如果我們還是按前面的辦法去取,框架就暈圈了,多個實現類,我給你哪一個呢?麻煩再明確一下吧,ok嗎

Guice有個註解,叫Named,可以加在各種地方,註解本身,支援設定名稱。

這裡意思就是說,你介面不是多個實現嗎,那我們這樣:介面+註解,才算是唯一的key,這樣ok了吧。

因此,現在就變成了這樣:介面+註解1 --》 實現類1;介面 + 註解2 --》 實現類2.

那我怎麼取呢?簡單啊,怎麼存,怎麼取,存的時候,用的介面+註解,取的時候,也需要這樣。

既然,可以用官方的Named註解,那其實自己的註解也一樣可以用。

3. InstanceBindings 介面直接繫結一個單例物件

如果同一個型別,要繫結到多個例項的情況,同前面的處理方式一樣。

4. 繫結到工廠方法:授人以魚不如授人以漁

前面都是些直來直去的辦法,這次不一樣,我只告訴你,這個東西的獲得方法。

5. 不用介面了,直接繫結一個實現類

前面都是根據一個介面類,去取介面對應的實現之類的。這次不一樣,直接就是一個實現類了。

public class UtilService {
}

像上面這個情況,那肯定是直接呼叫這個類的建構函式了。

6. 介面繫結到一個建構函式:ToConstructorBindings

哎,我是越來越無語了,Guice的騷操作真是多啊。

7. 內建的不用綁就能用的

主要有:Logger、Injector本身(就是相當於可以幫你注入容器自身)

8. 能不能不繫結直接用

用慣了spring的我們,現在已經是不需要這麼綁來綁去了。我們看看Guice的支援怎麼樣

不繫結的話,可以這樣:

@ImplementedBy(TestInterfaceImpl.class)
interface TestInterface {
}

這就相當於,你要自己指定一下,你的實現類是誰,或者你的provider是誰。但是官方不建議用這種隱式繫結,不知道為啥,還出了個選項,專門禁用隱式繫結。

9. 一個介面多個實現類,一次性全獲取回來

這個場景,就是一次性把多實現類一把取回來,放到一個集合裡給你。

這個場景我沒寫程式碼,大家自己看一下文件,也簡單。

10. 注入的方式

前面說了很多怎麼手動從容器裡面取,當然了,要自動注入的話,也是支援:構造器注入、field注入等等方式。

如以下為構造器注入:

其他支援的特性

其他的,比如迴圈依賴、aop也是大體支援的,只是這個容器在安卓端用,會有問題,因為aop好像不太支援,所以給安卓端還專供了一個去掉aop的版本。

迴圈依賴之類的,具體實現還沒怎麼看過。

另外,guice預設生成的是多例(類比spring的prototype,而不是singleton),但是本身也是支援singleton的,我前面的程式碼例子有。

最大槽點

可以看出,Guice是很輕量,輕量的意思是,功能沒Spring那麼全,所以,我們還需要去顯式地:配置每個介面,要怎麼獲取它的物件(方法也是五花八門,哈哈哈,如前面展示的)。

當然,配置ok後,我們獲取某物件的時候,它會幫我們去完成自動注入的東西。

但是,它也不支援類路徑掃描啊,比如給個包名,自動去掃描這個包下面的元件,反正官方不支援,說是有第三方外掛,還沒試過。

總結

在各種輪子裡,用來管理自己的程式碼間的相互依賴,用Guice確實足夠了,用在業務程式碼,就還是有點累。

因為,主要是:各種依賴要自己配,只是集中在一個地方配置而已,沒有像spring那樣,約定通過介面找物件時,預設就是找實現類,然後反射生成物件。

這一點來說,確實是:約定優於配置,就像Maven為啥打敗了ant,也是這個道理。

另外的問題就是,不支援spring的那種component-scan。

基於這兩個問題呢,方法肯定是有的,所以,Maven也足夠聰明,沒有直接基於Guice,而是選擇了基於Guice封裝後的Sisu,而Sisu就可以解決我們說的問題,支援類路徑掃描之類的。

我們看看sisu怎麼介紹自己:

就是比Guice多了些看起來還很不錯的、Spring早已有了的特性吧。回頭我們肯定要再介紹sisu的。

再見,以上。

相關文章