Container 是一款爲 Go 語言開發的運行時依賴注入庫。Go 語言的語言特性決定了實現一款類型安全的依賴注入容器並不太容易,所以 Container 大量使用了 Go 的反射機制。若是你的使用場景對性能要求並非那個苛刻,那 Container 很是適合你。mysql
並非說對性能要求苛刻的環境中就不能使用了,你能夠把 Container 做爲一個對象依賴管理工具,在你的業務初始化時獲取依賴的對象。
使用方式git
go get github.com/mylxsw/container
要建立一個 Container 實例,使用 containier.New
方法github
cc := container.New()
此時就建立了一個空的容器。web
你也可使用container.NewWithContext(ctx)
來建立容器,建立以後,能夠自動的把已經存在的context.Context
對象添加到容器中,由容器託管。
在使用以前,咱們須要先將咱們要託管的對象告訴容器。Container 支持三種類型的對象管理sql
Singleton
Prototype
Value
全部的對象綁定方法都會返回一個error
返回值來講明是否綁定成功,應用在使用時必定要主動去檢查這個error
。肯定對象必定會綁定成功(通常不違反文檔中描述的參數簽名方式,都是必定會成功的)或者要求對象必需要綁定成功(一般咱們都要求這樣,否則怎麼進行依賴管理呢),則可使用
Must
系列方法,好比Singleton
方法對應的時MustSingleton
,當建立出錯時,該方法會直接panic
。json
綁定對象時,Singleton
,Prototype
,BindValue
方法對於同一類型,只能綁定一次,若是屢次綁定同一類型對象的建立函數,會返回 ErrRepeatedBind
錯誤。數組
有時候,但願對象建立函數能夠屢次從新綁定,這樣就能夠個應用更多的擴展性,能夠隨時替換掉對象的建立方法,好比測試時 Mock
對象的注入。這時候咱們可使用 Override
系列方法:緩存
SingletonOverride
PrototypeOverride
BindValueOverride
使用 Override
系列方法時,必須保證第一次綁定時使用的是 Override
系列方法,不然沒法從新綁定。安全
也就是說,能夠這樣綁定SingletonOverride
->SingletonOverride
,SingletonOverride
->Singleton
,可是一旦出現Singleton
,後續就沒法對該對象從新綁定了。
使用 Singleton
系列的方法來將單例對象託管給容器,單例對象只會在第一次使用時自動完成建立,以後全部對該對象的訪問都會自動將已經建立好的對象注入進來。服務器
經常使用的方法是 Singleton(initialize interface{}) error
方法,該方法會按照你提供的 initialize
函數或者對象來完成單例對象的註冊。
參數 initialize
支持如下幾種形式:
對象建立函數 func(deps...) 對象返回值
好比
cc.Singleton(func() UserRepo { return &userRepoImpl{} }) cc.Singleton(func() (*sql.DB, error) { return sql.Open("mysql", "user:pwd@tcp(ip:3306)/dbname") }) cc.Singleton(func(db *sql.DB) UserRepo { // 這裏咱們建立的 userRepoImpl 對象,依賴 sql.DB 對象,只須要在函數 // 參數中,將依賴列舉出來,容器會自動完成這些對象的建立 return &userRepoImpl{db: db} })
帶錯誤返回值的對象建立函數 func(deps...) (對象返回值, error)
對象建立函數最多支持兩個返回值,且要求第一個返回值爲指望建立的對象,第二個返回值爲 error 對象。
cc.Singleton(func() (Config, error) { // 假設咱們要建立配置對象,該對象的初始化時從文件讀取配置 content, err := ioutil.ReadFile("test.conf") if err != nil { return nil, err } return config.Load(content), nil })
直接綁定對象
若是對象已經建立好了,想要讓 Container 來管理,能夠直接將對象傳遞 Singleton
方法
userRepo := repo.NewUserRepo() cc.Singleton(userRepo)
當對象第一次被使用時, Container 會將對象建立函數的執行結果緩存起來,從而實現任什麼時候候後訪問都是獲取到的同一個對象。
原型對象(多例對象)是指的由 Container 託管對象的建立過程,可是每次使用依賴注入獲取到的都是新建立的對象。
使用 Prototype
系列的方法來將原型對象的建立託管給容器。經常使用的方法是 Prototype(initialize interface{}) error
。
參數 initialize
能夠接受的類型與 Singleton
系列函數徹底一致,惟一的區別是在對象使用時,單例對象每次都是返回的同一個對象,而原型對象則是每次都返回新建立的對象。
這種綁定方式是將某個對象綁定到 Container 中,可是與 Singleton
系列方法不一樣的是,它要求必須指定一個字符串類型的 Key
,每次獲取對象的時候,使用 Get
系列函數獲取綁定的對象時,直接傳遞這個字符串 Key 便可。
經常使用的綁定方法爲 BindValue(key string, value interface{})
。
cc.BindValue("version", "1.0.1") cc.MustBindValue("startTs", time.Now()) cc.BindValue("int_val", 123)
在使用綁定對象時,一般咱們使用 Resolve
和 Call
系列方法。
Resolve(callback interface{}) error
方法執行體 callback 內部只能進行依賴注入,不接收注入函數的返回值,雖然有一個 error
返回值,可是該值只代表是否在注入對象時產生錯誤。
好比,咱們須要獲取某個用戶的信息和其角色信息,使用 Resolve 方法
cc.MustResolve(func(userRepo repo.UserRepo, roleRepo repo.RoleRepo) { // 查詢 id=123 的用戶,查詢失敗直接panic user, err := userRepo.GetUser(123) if err != nil { panic(err) } // 查詢用戶角色,查詢失敗時,咱們忽略了返回的錯誤 role, _ := roleRepo.GetRole(user.RoleID) // do something you want with user/role })
直接使用 Resolve
方法可能並不太知足咱們的平常業務需求,由於在執行查詢的時候,老是會遇到各類 error
,直接丟棄會產生不少隱藏的 Bug,可是咱們也不傾向於使用 Panic
這種暴力的方式來解決。
Container 提供了 ResolveWithError(callback interface{}) error
方法,使用該方法時,咱們的 callback 能夠接受一個 error
返回值,來告訴調用者這裏出現問題了。
err := cc.ResolveWithError(func(userRepo repo.UserRepo, roleRepo repo.RoleRepoo) error { user, err := userRepo.GetUser(123) if err != nil { return err } role, err := roleRepo.GetRole(user.RoleID) if err != nil { return err } // do something you want with user/role return nil }) if err != nil { // 自定義錯誤處理 }
Call(callback interface{}) ([]interface{}, error)
方法不只完成對象的依賴注入,還會返回 callback
的返回值,返回值爲數組結構。
好比
results, err := cc.Call(func(userRepo repo.UserRepo) ([]repo.User, error) { users, err := userRepo.AllUsers() return users, err }) if err != nil { // 這裏的 err 是依賴注入過程當中的錯誤,好比依賴對象建立失敗 } // results 是一個類型爲 []interface{} 的數組,數組中按次序包含了 callback 函數的返回值 // results[0] - []repo.User // results[1] - error // 因爲每一個返回值都是 interface{} 類型,所以在使用時須要執行類型斷言,將其轉換爲具體的類型再使用 users := results[0].([]repo.User) err := results[0].(error)
有時咱們但願爲不一樣的功能模塊綁定不一樣的對象實現,好比在 Web 服務器中,每一個請求的 handler 函數須要訪問與本次請求有關的 request/response 對象,請求結束以後,Container 中的 request/response 對象也就沒有用了,不一樣的請求獲取到的也不是同一個對象。咱們可使用 CallWithProvider(callback interface{}, provider func() []*Entity) ([]interface{}, error)
配合 Provider(initializes ...interface{}) (func() []*Entity, error)
方法實現該功能。
ctxFunc := func() Context { return ctx } requestFunc := func() Request { return ctx.request } provider, _ := cc.Provider(ctxFunc, requestFunc) results, err := cc.CallWithProvider(func(userRepo repo.UserRepo, req Request) ([]repo.User, error) { // 這裏咱們注入的 Request 對象,只對當前 callback 有效 userId := req.Input("user_id") users, err := userRepo.GetUser(userId) return users, err }, provider)
使用 AutoWire
方法能夠爲結構體的屬性注入其綁定的對象,要使用該特性,咱們須要在須要依賴注入的結構體對象上添加 autowire
標籤。
type UserManager struct { UserRepo *UserRepo `autowire:"@" json:"-"` field1 string `autowire:"version"` Field2 string `json:"field2"` } manager := UserManager{} // 對 manager 執行 AutoWire 以後,會自動注入 UserRepo 和 field1 的值 if err := c.AutoWire(&manager); err != nil { t.Error("test failed") }
結構體屬性注入支持公開和私有字段的注入。若是對象是經過類型來注入的,使用 autowire:"@"
來標記屬性;若是使用的是 BindValue
綁定的字符串爲key的對象,則使用 autowire:"Key名稱"
來標記屬性。
因爲AutoWire
要修改對象,所以必須使用對象的指針,結構體類型必須使用&
。
方法簽名
HasBound(key interface{}) bool HasBoundValue(key string) bool
用於判斷指定的 Key 是否已經綁定過了。
方法簽名
Keys() []interface{}
獲取全部綁定到 Container 中的對象信息。
方法簽名
CanOverride(key interface{}) (bool, error)
判斷指定的 Key 是否能夠覆蓋,從新綁定建立函數。
Extend
並非 Container 實例上的一個方法,而是一個獨立的函數,用於從已有的 Container 生成一個新的 Container,新的 Container 繼承已有 Container 全部的對象綁定。
Extend(c Container) Container
容器繼承以後,在依賴注入對象查找時,會優先從當前 Container 中查找,當找不到對象時,再從父對象查找。
在 Container 實例上個,有一個名爲
ExtendFrom(parent Container)
的方法,該方法用於指定當前 Container 從 parent 繼承。
簡單的示例能夠參考項目的 example 目錄。
如下項目中使用了 Container
做爲依賴注入管理庫,感興趣的能夠參考一下。
Logstash
來完成業務和錯誤日誌的報警,配合Prometheus
,OpenFalcon
等主流監控框架完成服務級的報警。目前還在開發中,但基本功能已經可用。