Container - 为 Go语言而生的运行时依赖注入容器

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,当建立出错时,该方法会直接 panicjson

绑定对象时,SingletonPrototypeBindValue 方法对于同一类型,只能绑定一次,若是屡次绑定同一类型对象的建立函数,会返回 ErrRepeatedBind 错误。数组

有时候,但愿对象建立函数能够屡次从新绑定,这样就能够个应用更多的扩展性,能够随时替换掉对象的建立方法,好比测试时 Mock 对象的注入。这时候咱们可使用 Override 系列方法:缓存

  • SingletonOverride
  • PrototypeOverride
  • BindValueOverride

使用 Override 系列方法时,必须保证第一次绑定时使用的是 Override 系列方法,不然没法从新绑定。安全

也就是说,能够这样绑定 SingletonOverride -> SingletonOverrideSingletonOverride -> 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)

依赖注入

在使用绑定对象时,一般咱们使用 ResolveCall 系列方法。

Resolve

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

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 是否能够覆盖,从新绑定建立函数。

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 来完成业务和错误日志的报警,配合PrometheusOpenFalcon 等主流监控框架完成服务级的报警。目前还在开发中,但基本功能已经可用。
  • Sync 使用 Glacier 开发一款跨主机文件同步工具,拥有友好的 web 配置界面,使用 GRPC 实现不一样服务器之间文件的同步。
相关文章
相关标签/搜索