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
等主流监控框架完成服务级的报警。目前还在开发中,但基本功能已经可用。