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


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的。

再見,以上。


免責聲明!

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



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