Go-IOC —— 为 Go 开发的依赖注入容器
Go-IOC 是一款为 Go 语言开发的运行时依赖注入库。Go 语言的语言特性决定了实现一款类型安全的依赖注入容器并不太容易,因此 Go-IOC 大量使用了 Go 的反射机制。如果你的使用场景对性能要求并不是那个苛刻,那 Go-IOC 非常适合你。
并不是说对性能要求苛刻的环境中就不能使用了,你可以把 Go-IOC 作为一个对象依赖管理工具,在你的业务初始化时获取依赖的对象。
使用方式
go get github.com/mylxsw/go-ioc
要创建一个 Container 实例,使用 ioc.New
方法
cc := ioc.New()
此时就创建了一个空的容器。
你也可以使用
ioc.NewWithContext(ctx)
来创建容器,创建之后,可以自动的把已经存在的context.Context
对象添加到容器中,由容器托管。
对象绑定
在使用之前,我们需要先将我们要托管的对象告诉容器。Container 支持三种类型的对象管理
- 单例对象
Singleton
- 原型对象(多例对象)
Prototype
- 字符串值对象绑定
Value
所有的对象绑定方法都会返回一个
error
返回值来说明是否绑定成功,应用在使用时一定要主动去检查这个error
。确定对象一定会绑定成功(一般不违反文档中描述的参数签名方式,都是一定会成功的)或者要求对象必须要绑定成功(通常我们都要求这样,不然怎么进行依赖管理呢),则可以使用
Must
系列方法,比如Singleton
方法对应的时MustSingleton
,当创建出错时,该方法会直接panic
。
绑定对象时,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
Resolve(callback interface{}) error
方法执行体 callback 内部能够进行依赖注入,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 }) err := cc.Resolve(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
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)
Provider
有时我们希望为不同的功能模块绑定不同的对象实现,比如在 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
方法可以为结构体的属性注入其绑定的对象,要使用该特性,我们需要在需要依赖注入的结构体对象上添加 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/HasBoundValue
方法签名
HasBound(key interface{}) bool HasBoundValue(key string) bool
用于判断指定的 Key 是否已经绑定过了。
Keys
方法签名
Keys() []interface{}
获取所有绑定到 Container 中的对象信息。
CanOverride
方法签名
CanOverride(key interface{}) (bool, error)
判断指定的 Key 是否可以覆盖,重新绑定创建函数。
WithCondition
WithCondition
并不是 Container 实例的一个方法,而是一个工具函数,用于创建 Conditional
接口。实现 Conditional
接口后,在创建实例方法时会根据指定条件是否为 true 来判断当前实例方法是否有效。
WithCondition(init interface{}, onCondition interface{}) Conditional
参数 init
是传递给 Singleton
和 Prototype
方法的实例创建方法,onCondition
参数则是一个条件,在调用 Singleton
及 Prototype
方法时,会执行 onCondition
函数,该函数支持两种形式
onCondition(依赖注入参数列表...) bool
onCondition(依赖注入参数列表...) (bool, error)
onCondition
函数的 bool 返回值用于控制该实例方法是否生效。
Extend
Extend
并不是 Container 实例上的一个方法,而是一个独立的函数,用于从已有的 Container 生成一个新的 Container,新的 Container 继承已有 Container 所有的对象绑定。
Extend(c Container) Container
容器继承之后,在依赖注入对象查找时,会优先从当前 Container 中查找,当找不到对象时,再从父对象查找。
在 Container 实例上个,有一个名为
ExtendFrom(parent Container)
的方法,该方法用于指定当前 Container 从 parent 继承。
示例项目
简单的示例可以参考项目的 example 目录。
以下项目中使用了 Container
作为依赖注入管理库,感兴趣的可以参考一下。
- Glacier 一个应用管理框架,目前还没有写使用文档,该框架集成了 Container,用来管理框架的对象实例化。
- Adanos-Alert 使用 Glacier 开发的一款报警系统,它侧重点并不是监控,而是报警,可以对各种报警信息进行聚合,按照配置规则来实现多样化的报警,一般用于配合
Logstash
来完成业务和错误日志的报警,配合Prometheus
,OpenFalcon
等主流监控框架完成服务级的报警。目前还在开发中,但基本功能已经可用。 - Sync 使用 Glacier 开发一款跨主机文件同步工具,拥有友好的 web 配置界面,使用 GRPC 实现不同服务器之间文件的同步。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
每日一博 | 单体分层应用架构剖析
分层单体架构风格是分层思想在单体架构中的应用,其关注于技术视角的职责分层。同时,基于不同层变化速率的不同,在一定程度上控制变化在系统内的传播,有助于提升系统的稳定性。但这种技术视角而非业务视角的关注点隔离,导致了问题域与工程实现之间的Gap,这种割裂会导致系统认知复杂度的提升。 作者:倪新明 1 经典单体分层架构 1.1 四层单体架构风格 经典的四层单体分层架构如下图所示,应用在逻辑上划分为展现层、业务层、持久层及数据存储层,每层的职责如下: • 展现层:负责给最终用户展现信息,并接受用户的输入触发系统的业务逻辑。用户可以是使用系统的人,也可以是其他软件系统。 • 业务层:关注系统业务逻辑的实现 • 持久层:负责数据的存取 • 数据存储层:底层的数据存储设施 这种分层单体架构可能是大多数开发人员最早接触、最为熟悉的应用架构风格,其特点是: • 层间的依赖关系由上到下逐层向下直接依赖,每层都是关闭状态,请求的数据流向从上到下,必须严格通过每个分层进行流转,而不能进行穿透调用。 • 关注点隔离:通过分层将系统的关注点进行垂直分配,每层只关注自身层边界内的职责,层间职责相互独立不...
- 下一篇
Elasticsearch 母公司 Elastic 宣布裁员 13%
Elastic 是开源搜索和数据分析引擎Elasticsearch 背后的母公司。今天 Elastic 的 CEO宣布了裁员计划,表示将裁减公司大约13% 的员工。 Elastic CEO 在发给全体员工的邮件中称,目前全球宏观经济环境正在迫使他们的客户收紧预算,并更仔细地审查投资。在市场的某些部分尤其如此,例如中小企业在不确定时期的消费意愿有限。为了渡过这个阶段,公司要将重心放在那些对未来最关键的业务领域,并找到更有效的方法来服务公司业务的某些部分。 Elastic 公司对被裁员工提供了如下方案: 遣散费:向所有被裁员工支付至少 14 周的补偿金,另外工作满一年就增加一周的补偿金。 PTO:对于所有未使用的 PTO 时间(Pay Time Off,带薪休假)支付相应的薪水。 医疗保健:对于参与 Elastic 公司医疗保健计划的员工,支付 6 个月的现有医疗保健保费,或将根据工作地点支付等值现金。 RSU 股票:被裁员工将会获得截至 12 月 8 日的 RSU 股票。 职业支持:为受影响的员工提供简历和求职支持。 移民支持:为有需要的人提供移民支持。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Hadoop3单机部署,实现最简伪集群
- CentOS关闭SELinux安全模块
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7