golang實現依賴注入
golang實現依賴注入
依賴注入是軟體工程中經常使用到的一種技術,它提供了一種控制反轉的機制,把控制權利交給了呼叫方。呼叫方來決定使用哪些引數,哪些物件來進行具體的業務邏輯。
它有幾個好處:
1 它讓呼叫方更靈活。
2 大量減少定義型別的程式碼量
3 增加程式碼的可用性,因為呼叫方只需要關注它需要的引數,不需要顧及它不需要的引數了。
什麼是依賴注入
依賴注入使用最多的應該是java中的spring框架了。依賴注入在使用的時候希望呼叫函式的引數是不固定的。
function Action(a TypeA, b TypeB)
就是說,這個Action在實際呼叫的時候,可以任意加引數,每次加一個引數型別,都有一個容器可以給這個Action呼叫函式傳遞對應的引數物件提供使用。
inject
Golang中也有專案是使用依賴注入實現的,martini就是一個依靠依賴注入實現的web框架,它的作者開源的https://github.com/codegangsta/inject 專案也就很值得我們學習。
這個inject專案很小,實際程式碼就一個檔案,很容易閱讀。
// Injector代表依賴注入的容器需要實現的介面 type Injector interface { Applicator // 這個介面用來灌入到一個結構體 Invoker // 這個介面用來實際呼叫的,所以可以實現非反射的實際呼叫 TypeMapper // 這個介面是真正的容器 // SetParent sets the parent of the injector. If the injector cannot find a // dependency in its Type map it will check its parent before returning an // error. SetParent(Injector) // 表示這個結構是遞迴的 }
這個Injector使用三個介面進行組合,每個介面有各自不同的用處。
TypeMapper是依賴注入最核心的容器部分,注入型別和獲取型別都是這個介面承載的。
Invoker和Applicator都是注入部分,Invoker將TypeMapper容器中的資料注入到呼叫函式中。而Applicator將容器中的資料注入到實體物件中。
最後我們還將Injector容器設計為有層級的,在我們獲取容器資料的時候,會先從當前容器找,找不到再去父級別容器中找。
這幾個介面中的TypeMapper又值得看一下:
// TypeMapper represents an interface for mapping interface{} values based on type. // TypeMapper是用來作為依賴注入容器的,設定的三種方法都是鏈式的 type TypeMapper interface { // Maps the interface{} value based on its immediate type from reflect.TypeOf. // 直接設定一個物件,TypeOf是key,value是這個物件 Map(interface{}) TypeMapper // Maps the interface{} value based on the pointer of an Interface provided. // This is really only useful for mapping a value as an interface, as interfaces // cannot at this time be referenced directly without a pointer. // 將一個物件注入到一個介面中,TypeOf是介面,value是物件 MapTo(interface{}, interface{}) TypeMapper // Provides a possibility to directly insert a mapping based on type and value. // This makes it possible to directly map type arguments not possible to instantiate // with reflect like unidirectional channels. // 直接手動設定key和value Set(reflect.Type, reflect.Value) TypeMapper // Returns the Value that is mapped to the current type. Returns a zeroed Value if // the Type has not been mapped. // 從容器中獲取某個型別的注入物件 Get(reflect.Type) reflect.Value }
這裡的Map是將資料注入,即將資料型別和資料值進行對映儲存在容器中。MapTo是將資料介面和資料值進行對映儲存在容器中。Set就是手動將資料型別活著資料介面和資料值儲存在容器中。Get則和Set相反。
我們可以看下inject檔案中實現了這個介面的物件:injector
// 實際的注入容器,它實現了Injector的所有介面
type injector struct {
// 這個就是容器最核心的map
values map[reflect.Type]reflect.Value
// 這裡設定了一個parent,所以這個Inject是可以巢狀的
parent Injector
}
其中的這個map[reflect.Type]reflect.Value就是最核心的。那麼這裡就需要注意到了,這個inject實際上是一個基礎的map,而不是執行緒安全的map。所以如果在併發場景下,不應該在併發請求中進行動態注入或者改變容器元素。否則很有可能出現各種執行緒安全問題。
我們可以看看Map,Set等函式做的事情就是設定這個Map
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
下一個重要的函式就Invoke。
這個Invoke做的事情我們也能很容易想清,根據它本身裡面的函式引數型別,一個個去容器中拿對應值。
// 真實的呼叫某個函式f,這裡的f預設是function
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.Get(argType)
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
in[i] = val
}
return reflect.ValueOf(f).Call(in), nil
}
注:inject相關的中文註釋程式碼解讀在專案:https://github.com/jianfengye/inside-go 中。
go-macaron/inject
無聞在matini基礎上又封裝了一層inject。它使用的方法是直接保留CopyRight的通知,將https://github.com/codegangsta/inject 這個類做了一些修改。
我看了下這些修改,主要是增加了一個FastInvoker
// FastInvoker represents an interface in order to avoid the calling function via reflection.
//
// example:
// type handlerFuncHandler func(http.ResponseWriter, *http.Request) error
// func (f handlerFuncHandler)Invoke([]interface{}) ([]reflect.Value, error){
// ret := f(p[0].(http.ResponseWriter), p[1].(*http.Request))
// return []reflect.Value{reflect.ValueOf(ret)}, nil
// }
//
// type funcHandler func(int, string)
// func (f funcHandler)Invoke([]interface{}) ([]reflect.Value, error){
// f(p[0].(int), p[1].(string))
// return nil, nil
// }
type FastInvoker interface {
// Invoke attempts to call the ordinary functions. If f is a function
// with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
Invoke([]interface{}) ([]reflect.Value, error)
}
並且在Invoke呼叫的地方增加了一個分支,如果這個呼叫函式是自帶有Invoke方法的,那麼就用一種不用反射的方式。
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
switch v := f.(type) {
case FastInvoker:
return inj.fastInvoke(v, t, t.NumIn())
default:
return inj.callInvoke(f, t, t.NumIn())
}
}
我覺得這個fastInvoke是神來之筆啊。我們使用Golang的inject最害怕的就是效能問題。這裡的Invoke頻繁使用了反射,所以會導致Invoke的效能不會很高。但是我們有了fastInvoke替換方案,當需要追求效能的時候,我們就可以使用fastInvoke的方法進行替換。
示例
所以我下面的這個示例是最好的理解inject的例子:
package main
import "gopkg.in/macaron.v1"
import "github.com/go-macaron/inject"
import "fmt"
import "reflect"
type A struct {
Name string
}
type B struct {
Name string
}
func (b *B) GetName() string {
return b.Name
}
type I interface {
GetName() string
}
type C struct {
AStruct A `inject`
BStruct B `inject`
}
type MyFastInvoker func(arg1 A, arg2 I, arg3 string)
func (invoker MyFastInvoker) Invoke(args []interface{}) ([]reflect.Value, error) {
if a, ok := args[0].(A); ok {
fmt.Println(a.Name)
}
if b, ok := args[1].(I); ok {
fmt.Println(b.GetName())
}
if c, ok := args[2].(string); ok {
fmt.Println(c)
}
return nil, nil
}
type Invoker2 struct {
inject.Injector
}
func main() {
InjectDemo()
a := &A{Name: "inject name"}
m := macaron.Classic()
m.Map(a)
m.Get("/", func(a *A) string {
return "Hello world!" + a.Name
})
m.Run()
}
func InjectDemo() {
a := A{Name: "a name"}
inject1 := inject.New()
inject1.Map(a)
inject1.MapTo(&B{Name: "b name"}, (*I)(nil))
inject1.Set(reflect.TypeOf("string"), reflect.ValueOf("c name"))
inject1.Invoke(func(arg1 A, arg2 I, arg3 string) {
fmt.Println(arg1.Name)
fmt.Println(arg2.GetName())
fmt.Println(arg3)
})
c := C{}
inject1.Apply(&c)
fmt.Println(c.AStruct.Name)
inject2 := inject.New()
inject2.Map(a)
inject2.MapTo(&B{Name: "b name"}, (*I)(nil))
inject2.Set(reflect.TypeOf("string"), reflect.ValueOf("c name"))
inject2.Invoke(MyFastInvoker(nil))
}
輸出:
a name
b name
c name
a name
b name
c name
上面那個例子能看懂基本就掌握了inject的使用了