Golang中的反射和依赖注入
一切的源起,要从interface{}
说起。
接口
在Golang中,我们可以把任意类型赋值给interface{}
,它的底层由两部分组成,类型(type)
和值(data)
,type用于存储变量的动态类型,data用于存储变量的具体数据。
有时候你会在代码中看到下面这种用法。
(*http.ResponseWriter)(nil)
它表示将一个nil转化成了http.ResponseWriter类型的指针,它指向无效的地址,它的存储结构是(*http.ResponseWriter,nil)。通常这样的使用者只关心指针的类型,不关心它具体的存储值,后面你会看到这种用法的一个具体场景。
反射
如果我们知道一个接口的类型,就可以使用断言
来访问其内部的值,但如果不知道呢?那就试试反射。
反射是由reflect
包提供的,它定义了两个重要的类型: reflect.Type
和reflect.Value
。
reflect.Type是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。reflect.Type和接口的类型是动态相关的。reflect.TypeOf
方法接受一个接口类型的参数,并返回一个reflect.Type实例。
reflect.Value可以装载任意类型的值。reflect.ValueOf
方法接受一个任意的接口类型,并返回一个装载着其动态值的reflect.Value。函数reflect.ValueOf的逆方法是reflect.Value.Interface
方法,它返回一个接口。
var x interface{}
x = 1
fmt.Println(reflect.TypeOf(x), reflect.ValueOf(x))
fmt.Println(reflect.ValueOf(x).Interface().(int))
x = "a"
fmt.Println(reflect.TypeOf(x), reflect.ValueOf(x))
依赖注入
我以前很少会用到反射,觉得这个东西很复杂,并且影响性能,直到看到了这个包:https://github.com/go-macaron/inject,这是macaron框架中依赖注入器。最原始的实现在这里:https://github.com/codegangsta/inject,代码基本上差不多。
有了这个包,我们就可以使用反射的方式来调用函数,比如:
package main
import (
"fmt"
"github.com/go-macaron/inject"
)
type SpecialString interface{}
func Say(name string, gender SpecialString, age int) {
fmt.Printf("name:%s, gender:%s, age:%d!\n", name, gender, age)
}
func main() {
inj := inject.New()
inj.Map("itsmikej")
inj.MapTo("男", (*SpecialString)(nil))
inj.Map(26)
inj.Invoke(Say)
}
在inject内部,它使用了一个map[reflect.Type]reflect.Value
来存储注入的参数,Map
和MapTo
方法用于将参数注入到这个map中,注意上面的MapTo方法,它的第二个参数用来重新设置reflect.Type,避免同类型的值覆盖。如前文提到的,参数之所以是(*SpecialString)(nil),是因为这里我们只需要关心它的类型,也就是用它的reflect.Type来作为map的key。
那么这样调用的好处是什么?
一个典型的应用场景,在一个web服务中,不同的路由下处理的handler不一样,他们依赖的服务也不一样。使用inject可以很方便的将服务注入到handler中。
我们来看看inject在macaron框架中的应用,macaron实现了inject.Injector的接口,所以映射一个服务就变得非常简单:
db := &MyDatabase{}
m := macaron.Classic()
m.Map(db) // Service will be available to all handlers as *MyDatabase
m.Get("/", func(db *MyDatabase) {
// Operations with db.
})
m.Run()
这里先将db服务注入到map中,最终路由"/"
下的handler会被inject包中Invoke
方法调用。
看到下面的源码,可以暂时忽略fastInvoke
,这是macaron内部的一个快捷调用,Invoke
方法调用了callInvoke
方法,callInvoke
方法会先从map中找到对应的参数,然后用反射的方式来调用这个handler,从而实现了参数的动态注入。
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())
}
}
// callInvoke reflect.Value.Call
func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) {
var in []reflect.Value
if numIn > 0 {
in = make([]reflect.Value, numIn)
var argType reflect.Type
var val reflect.Value
for i := 0; i < numIn; i++ {
argType = t.In(i)
val = inj.GetVal(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
}
macaron.Classic
默认会注入一些服务,包括:
*macaron.Context
- HTTP 请求上下文*log.Logger
- Macaron 全局日志器http.ResponseWriter
- HTTP 响应流*http.Request
- HTTP 请求对象
所以我们可以直接这样使用:
m.Get("/", func(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(200) // HTTP 200
})
// 或者
m.Get("/", func(ctx *macaron.Context) {
ctx.Resp.WriteHeader(200) // HTTP 200
})
是不是很酷?
参考
说得可能不是很明白,可以结合下面几篇文章来看看。
https://docs.hacknode.org/gopl-zh/ch7/ch7-05.html
https://docs.hacknode.org/gopl-zh/ch12/ch12-02.html
https://my.oschina.net/goal/blog/195036
https://my.oschina.net/goal/blog/194233
https://go-macaron.com/docs/advanced/custom_services