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的。
再見,以上。