当前位置: 首页 > 软件库 > 程序开发 > >

Go-IOC

为 Go 开发的依赖注入容器
授权协议 MIT
开发语言 Google 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

绑定对象时,SingletonPrototypeBindValue 方法对于同一类型,只能绑定一次,如果多次绑定同一类型对象的创建函数,会返回 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 来完成业务和错误日志的报警,配合PrometheusOpenFalcon 等主流监控框架完成服务级的报警。目前还在开发中,但基本功能已经可用。
  • Sync 使用 Glacier 开发一款跨主机文件同步工具,拥有友好的 web 配置界面,使用 GRPC 实现不同服务器之间文件的同步。
  • 1. iocgo简介 习惯于Java或者C#开发的人应该对控制反转与依赖注入应该再熟悉不过了。在Java平台有鼎鼎大名的Spring框架,在C#平台有Autofac,Unity,Windsor等,我当年C#开发时用的最多的就是Windsor。使用IoC容器是面向对象开发中非常方便的解耦模块之间的依赖的方法。各个模块之间不依赖于实现,而是依赖于接口,然后在构造函数或者属性或者方法中注入特定的实现,方

  • 本文目的 向大家介绍: 在开发中为何要使用IoC 如何从0开始实现一个精简的IoC 使用IoC前后代码带来怎样的变化 我当前在开发的IoC类库 如果你对1、2、3都已经很熟了,并且对我的项目感兴趣,可以直接跳我的IoC仓库.完整的工程地址在https://github.com/kakashiio/Unity-IOC,该IoC仓库也是我的Unity游戏框架计划https://github.com/k

  • 介绍Spring-IOC Spring-IOC(Inverse Of Control)控制反转。 IOC主要目的就是解耦。 Spring容器借助配置文档将相应的类实例化,进行注入构造器,属性和接口。 立即加载和懒加载 立即加载:立即加载就是表关联的时候,查询一个对象,会把他关联的对象都查出来初始化到属性中去,这个就是立即加载,所以在查询的时候可能出现多条查询语句 懒加载:懒加载在你进行数据库查询的

  • Spring - IOC Spring是一个开源免费的框架 , 容器 . Spring是一个轻量级的框架 , 非侵入式的 . 控制反转 IoC , 面向切面 Aop 对事物的支持 , 对框架的支持 支持事物的处理,支持框架整合的支持 一句话概括: Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。 IOC 控制反转IoC(Inversion of Control),是

  • 前面共同学习长安链ioc如何使用,下面聊聊IOC的实现原理。本节主要分析两个方法,Register、Resolve。 1.func (c *Container) Register(constructor interface{}, options ...Option) error 第一个参数:constructor ,某实现的构造方法,例如:NewFileStore 第二个参数:Option,例如:

  •     bean管理的集合属性注入,与单属性注入类型,稍有不同。小编在上篇文章:Spring — IOC 容器 之 Bean管理XML方式(属性注入) 介绍了单属性注入,今天来介绍一下集合属性的注入。Let go! 1,xml 配置文件:UserBeanConfig.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://ww

  •     关于Spring IOC,大家应该都不陌生,但是有没有像小编之前一样,知道它这个含义,但就时很难理解,很难个人操作去使用呢?今天,小编来演示一下 ioc 的简单使用,后面会陆续推出相关文章,更加深入全面的了解spring ioc。那么现在,Let go! 1,传统方式对类的使用演示: public class BeanTest { /** * 传统方式中,使用一个类,需要通过

  •     工厂bean的使用,可以实现获取不同于声明实体类的类,前面的文章:Spring — IOC 容器 之 Bean管理XML方式(创建对象) 获取的类即是声明的类,但是当声明的类实现 FactoryBean 接口,即可使得调用方法获取不同的类。下面小编来演示一下,Let go! 1,xml 文件配置:BeanConfig.xml <?xml version="1.0" encoding="UT

  • Configuration(args...): 代码会自动调用结构中"方法名与返回值类型名称一致的方法,进行初始化beanMap" Set(args...): 该注入方式支持使用结构体中tag,其标记为:inject,默认标记值为`inject:"auto"`或`inject:""`或不写tag,以上方式都以单例方式注入, 若填入标记的值为"结构.方法的类型",如:`inject:"Servic

 相关资料
  • 问题内容: 我的理解: 依赖关系是当ClassA实例需要ClassB实例实例化ClassA的新实例时。 依赖项注入是通过ClassA的构造函数中的参数或通过set〜DependencyNameHere〜(〜DependencyNameHere〜$ param)函数将ClassA传递给ClassB的实例时进行的。 (这是我不确定的领域之一) 。 IoC容器是单例类(在任何给定时间只能实例化1个实例)

  • 主要内容:inject 实践,inject 原理分析在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。 正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。 所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。 依

  • 本文向大家介绍.NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI),包括了.NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI)的使用技巧和注意事项,需要的朋友参考一下 依赖倒置原则(DIP) 依赖倒置(Dependency Inversion Principle,缩写DIP)是面向对象六大基本原则之一。他是指一种特定的的解耦形式,使得高层次的模块不依

  • 容器和依赖注入 5.1版本正式引入了容器的概念,用来更方便的管理类依赖及运行依赖注入。 5.0版本已经支持依赖注入的,依赖注入和容器没有必然关系 容器类的工作由think\Container类完成,但大多数情况我们只需要通过app助手函数即可完成大部分操作。 依赖注入其实本质上是指对类的依赖通过构造器完成自动注入,例如在控制器架构方法和操作方法中一旦对参数进行对象类型约束则会自动触发依赖注入,由于

  • 本文向大家介绍Spring的IOC和依赖注入之间的区别。,包括了Spring的IOC和依赖注入之间的区别。的使用技巧和注意事项,需要的朋友参考一下 控制反转是一种设计原则,有助于反转对象创建的控制。 根据马丁·福勒(Martin Fowler)的论文,控制反转是程序控制流反转的原理:外部源(框架,服务,其他组件)代替程序控制程序流,而由程序控制流它。就像我们将某些东西插入其他东西一样。他提到了有关

  • 依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。 Martin 的文章 已经解释了 DI 容器为什么很有用。 这里我们主要讲解 Yii 提供的 DI 容器的使用方法。 依赖注入(Dependency Injection) Yii 通过 yii\di\Container 类提供 DI 容器特性。 它支持如下几种类型的依赖注入: