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的使用了。


免責聲明!

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



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