深入了解Go语言中goioc框架的使用

目录
  • goioc 介绍
    • 快速上手
    • 接口介绍
  • 使用 goioc
    • 如何使用
    • 生命周期
    • 实例化
    • 获取对象
    • 结构体字段依赖注入
  • 反射形式使用 goioc
    • 如何使用
    • 接口、结构体、结构体指针
    • 不会自动注入本身

goioc 介绍

goioc 是一个基于 GO 语言编写的依赖注入框架,基于反射来进行编写。

  • 支持泛型;
  • 简单易用的 API;
  • 简易版本的对象生命周期管理,作用域内对象具有生命;
  • 延迟加载,在需要的时候才会实例化对象;
  • 支持结构体字段注入,多层注入;
  • 对象实例化线程安全,作用域内只会被执行一次。

下载依赖:

go get -u github.com/whuanle/goioc v2.0.0

快速上手

定义接口:

type IAnimal interface {
	Println(s string)
}

实现接口:

type Dog struct {
}
func (my Dog) Println(s string) {
	fmt.Println(s)
}

依赖注入以及使用:

// 注册容器
var sc goioc.IServiceCollection = &ServiceCollection{}
// 注入服务
goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
// 构建提供器
p := sc.Build()
// 获取服务
obj := goioc.Get[IAnimal](p)

接口介绍

IServiceCollection 是一个容器接口,通过此接口,将需要进行依赖注入的对象注册到容器中。

IServiceProvider 是一个服务提供器,当服务注册到容器后,构建一个服务提供器,IServiceProvider 可以管理服务的生命周期以及提供服务。

IDispose 接口用于声明此对象在 IServiceProvider 结束时,需要执行接口释放对象。

// IDispose 释放接口
type IDispose interface {
	// Dispose 释放资源
	Dispose()
}

除此之外,goioc 中还定义了部分扩展函数,如泛型注入等,代码量不多,简单易用。

使用 goioc

如何使用

注入的服务有两种形式,第一种是 B:A,即 B 实现了 A,使用的时候获取 A ;第二种是注入 B,使用的时候获取 B。

// 第一种
AddServiceOf[A,B]()
// 第二种
AddService[B]()

A 可以是接口或结构体,只要 B 实现了 A 即可。

定义一个接口:

type IAnimal interface {
	Println(s string)
}

实现这个接口:

type Dog struct {
	Id int
}

func (my Dog) Println(s string) {
	fmt.Println(s)
}

当使用依赖注入框架时,我们可以将接口和实现分开,甚至放到两个模块中,可以随时替换接口的实现。

注册服务和获取服务的代码示例如下:

func Demo() {
	sc := &ServiceCollection{}
	goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
	p := sc.Build()
	animal := goioc.GetI[IAnimal](p)
	animal.Println("test")
}

下面讲解编码过程。

首先创建 IServiceCollection 容器,容器中可以注册服务。

sc := &ServiceCollection{}

然后通过接口注入服务:

goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)

这个函数是泛型方法。如果不使用泛型,则注入过程麻烦得多。

注册完毕后,开始构建提供器:

p := sc.Build()

然后获取服务:

	animal := goioc.GetI[IAnimal](p)
	animal.Println("test")

生命周期

goioc 中定义了三个生命周期:

const (
	Transient ServiceLifetime = iota
	Scope
	Singleton
)

Transient:瞬时模式,每次获取到的都是新的对象;

Scope:作用域模式,同一个 Provider 中获取到的是同一个对象。

Singleton:单例模式,同一个 ServiceCollection 获取到的是同一个对象,也就是所有 Provider 获取到的都是同一个对象。

如果是单例模式(Singleton),那么无论多少次 Build,对象始终是同一个:

在注册服务的时候,需要注明对象生命周期。

goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)

生命周期为 scope 的注入,同一个 Provider 中,获取到的对象是一样的。

	sc := &ServiceCollection{}
	goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
	p := sc.Build()

    // 第一次获取对象
	animal1 := goioc.GetI[IAnimal](p)
	if animal1 == nil {
		t.Errorf("service is nil!")
	}
	animal1.Println("test")

    // 第二次获取对象
	animal2 := goioc.GetI[IAnimal](p)
	if animal2 == nil {
		t.Errorf("service is nil!")
	}

    // animal1 和 animal2 引用了同一个对象
	if animal1 != animal2 {
		t.Errorf("animal1 != animal2")
	}

实例一,Scope 生命周期的对象,在同一个提供器下获取到的都是同一个对象。

	sc := &ServiceCollection{}
    goioc.AddServiceHandlerOf[IAnimal, Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} {
		return &Dog{
			Id: 3,
		}
	})

	p := sc.Build()

	// 第一次获取
	a := goioc.GetI[IAnimal](p)

	if v := a.(*Dog); v == nil {
		t.Errorf("service is nil!")
	}
	v := a.(*Dog)
	if v.Id != 2 {
		t.Errorf("Life cycle error")
	}
	v.Id = 3

	// 第二次获取
	aa := goioc.GetI[IAnimal](p)
	v = aa.(*Dog)
	if v.Id != 3 {
		t.Errorf("Life cycle error")
	}

	// 重新构建的 scope,不是同一个对象
	pp := sc.Build()
	aaa := goioc.GetI[IAnimal](pp)
	v = aaa.(*Dog)
	if v.Id != 2 {
		t.Errorf("Life cycle error")
	}

实例二, ServiceCollection 构建的提供器,单例模式下获取到的都是同一个对象。

	sc := &ServiceCollection{}
	goioc.AddServiceHandler[Dog](sc, goioc.Singleton, func(provider goioc.IServiceProvider) interface{} {
		return &Dog{
			Id: 2,
		}
	})

	p := sc.Build()
	b := goioc.GetS[Dog](p)
	if b.Id != 2 {
		t.Errorf("Life cycle error")
	}

	b.Id = 3

	bb := goioc.GetS[Dog](p)
	if b.Id != bb.Id {
		t.Errorf("Life cycle error")
	}
	ppp := sc.Build()

	bbb := goioc.GetS[Dog](ppp)
	if b.Id != bbb.Id {
		t.Errorf("Life cycle error")
	}

实例化

由开发者决定如何实例化一个对象。

主要由注册形式决定,有四个泛型函数实现注册服务:

// AddService 注册对象
func AddService[T any](con IServiceCollection, lifetime ServiceLifetime)

// AddServiceHandler 注册对象,并自定义如何初始化实例
func AddServiceHandler[T any](con IServiceCollection, lifetime ServiceLifetime, f func(provider IServiceProvider) interface{})

// AddServiceOf 注册对象,注册接口或父类型及其实现,serviceType 必须实现了 baseType
func AddServiceOf[I any, T any](con IServiceCollection, lifetime ServiceLifetime)

// AddServiceHandlerOf 注册对象,注册接口或父类型及其实现,serviceType 必须实现了 baseType,并自定义如何初始化实例
func AddServiceHandlerOf[I any, T any](con IServiceCollection, lifetime ServiceLifetime, f func(provider IServiceProvider) interface{})

AddService[T any]:只注册可被实例化的对象:

AddService[T any]
goioc.AddService[Dog](sc, goioc.Scope)

AddServiceHandler 注册一个接口或结构体,自定义实例化。

func(provider goioc.IServiceProvider) interface{} 函数会在实例化对象时执行。

	goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} {
		return &Dog{
			Id: 1,
		}
	})

在实例化时,如果这个对象还依赖其他服务,则可以通过 goioc.IServiceProvider 来获取其他依赖。

例如下面示例中,一个依赖另一个对象,可以自定义实例化函数,从容器中取出其他依赖对象,然后构建一个新的对象。

	goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} {
		a := goioc.GetI[IA](provider)
		return &Dog{
			Id: 1,
            A:	a,
		}
	})
	goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} {
		config := goioc.GetI[Config](provider)
		if config.Enable == false
		return &Dog{
			Id: 1,
		}
	})

获取对象

前面提到,我们可以注入 [A,B],或者 [B]

那么获取的时候就有三种函数:

// Get 获取对象
func Get[T any](provider IServiceProvider) interface{} 

// GetI 根据接口获取对象
func GetI[T interface{}](provider IServiceProvider) T 

// GetS 根据结构体获取对象
func GetS[T interface{} | struct{}](provider IServiceProvider) *T

Get[T any] 获取接口或结构体,返回 interface{}

GetI[T interface{}] 获取的是一个接口实例。

GetS[T interface{} | struct{}] 获取的是一个结构体实例。

以上三种方式,返回的都是对象的引用,即指针。

	sc := &ServiceCollection{}
	goioc.AddService[Dog](sc, goioc.Scope)
	goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
	p := sc.Build()

	a := goioc.Get[IAnimal](p)
	b := goioc.Get[Dog](p)
	c := goioc.GetI[IAnimal](p)
	d := goioc.GetS[Dog](p)

结构体字段依赖注入

结构体中的字段,可以自动注入和转换实例。

如结构体 Animal 的定义,其使用了其它结构体,goioc 可以自动注入 Animal 对应字段,要被注入的字段必须是接口或者结构体。

// 结构体中包含了其它对象
type Animal struct {
	Dog IAnimal `ioc:"true"`
}

要对需要自动注入的字段设置 tag 中包含ioc:"true" 才会生效。

示例代码:

	sc := &ServiceCollection{}
	goioc.AddServiceHandlerOf[IAnimal, Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} {
		return &Dog{
			Id: 666,
		}
	})
	goioc.AddService[Animal](sc, goioc.Scope)

	p := sc.Build()
	a := goioc.GetS[Animal](p)
	if dog := a.Dog.(*Dog); dog.Id != 666 {
		t.Errorf("service is nil!")
	}

goioc 可以自动给你的结构体字段进行自动依赖注入。

注意,goioc 的字段注入转换逻辑是这样的。

如果 obj 要转换为接口,则是使用:

	animal := (*obj).(IAnimal)

如果 obj 要转换为结构体,则是:

	animal := (*obj).(*Animal)

反射形式使用 goioc

如何使用

goioc 的原理是反射,ioc 使用了大量的反射机制实现依赖注入,但是因为 Go 的反射比较难用,导致操作十分麻烦,因此使用泛型包装一层可以降低使用难度。

当然,也可以直接使用原生的反射方式进行依赖注入。

首先反射通过反射获取 reflect.Type

	// 获取 reflect.Type
	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()

依赖注入:

	// 创建容器
	sc := &ServiceCollection{}

	// 注入服务,生命周期为 scoped
	sc.AddServiceOf(goioc.Scope, imy, my)

	// 构建服务 Provider
	serviceProvider := sc.Build()

获取服务以及进行类型转换:

	// 获取对象
	// *interface{} = &Dog{},因此需要处理指针
	obj, err := serviceProvider.GetService(imy)
	animal := (*obj).(IAnimal)

示例:

	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()
	var sc IServiceCollection = &ServiceCollection{}
	sc.AddServiceOf(goioc.Scope,imy, my)
	p := sc.Build()

	// 获取对象
	// *interface{} = &Dog{},因此需要处理指针
	obj1, _ := p.GetService(imy)
	obj2, _ := p.GetService(imy)

	fmt.Printf("obj1 = %p,obj2 = %p\r\n", (*obj1).(*Dog), (*obj2).(*Dog))
	if fmt.Sprintf("%p",(*obj1).(*Dog)) != fmt.Sprintf("%p",(*obj2).(*Dog)){
		t.Error("两个对象不是同一个")
	}

获取接口和结构体的 reflect.Type:

// 写法 1
    // 接口的 reflect.Type
	var animal IAnimal
    imy := reflect.TypeOf(&animal).Elem()
	my := reflect.TypeOf(Dog{})

// 写法 2
	// 获取 reflect.Type
	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()

以上两种写法都可以使用,目的在于获取到接口和结构体的 reflect.Type。不过第一种方式会实例化结构体,消耗了一次内存,并且要获取接口的 reflect.Type,是不能直接有用 reflect.TypeOf(animal) 的,需要使用 reflect.TypeOf(&animal).Elem() 。

然后注入服务,其生命周期为 Scoped:

	// 注入服务,生命周期为 scoped
	sc.AddServiceOf(goioc.Scope, imy, my)

当你需要 IAnimal 接口时,会自动注入 Dog 结构体给 IAnimal。

构建依赖注入服务提供器:

	// 构建服务 Provider
	serviceProvider := sc.Build()

构建完成后,即可通过 Provider 对象获取需要的实例:

	// 获取对象
	// *interface{}
	obj, err := serviceProvider.GetService(imy)
	if err != nil {
		panic(err)
	}

	// 转换为接口
	a := (*obj).(IAnimal)
	// 	a := (*obj).(*Dog)

因为使用了依赖注入,我们使用时,只需要使用接口即可,不需要知道具体的实现。

完整的代码示例:

	// 获取 reflect.Type
	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()

	// 创建容器
	sc := &ServiceCollection{}

	// 注入服务,生命周期为 scoped
	sc.AddServiceOf(goioc.Scope, imy, my)

	// 构建服务 Provider
	serviceProvider := sc.Build()

	// 获取对象
	// *interface{} = &Dog{}
	obj, err := serviceProvider.GetService(imy)

	if err != nil {
		panic(err)
	}

	fmt.Println("obj 类型是", reflect.ValueOf(obj).Type())

	// *interface{} = &Dog{},因此需要处理指针
	animal := (*obj).(IAnimal)
	// 	a := (*obj).(*Dog)
	animal.Println("测试")

接口、结构体、结构体指针

在结构体注入时,可以对需要的字段进行自动实例化赋值,而字段可能有以下情况:

// 字段是接口
type Animal1 struct {
	Dog IAnimal `ioc:"true"`
}

// 字段是结构体
type Animal2 struct {
	Dog Dog `ioc:"true"`
}

// 字段是结构体指针
type Animal3 struct {
	Dog *Dog `ioc:"true"`
}

首先注入前置的依赖对象:

	// 获取 reflect.Type
	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()

	// 创建容器
    p := &ServiceCollection{}

	// 注入服务,生命周期为 scoped
	p.AddServiceOf(goioc.Scope,imy, my)
    p.AddService(goioc.Scope, my)

然后将我们的一些对象注入进去:

	t1 := reflect.TypeOf((*Animal1)(nil)).Elem()
	t2 := reflect.TypeOf((*Animal2)(nil)).Elem()
	t3 := reflect.TypeOf((*Animal3)(nil)).Elem()

	p.Ad(t1)
	p.AddServiceOf(goioc.Scope,t2)
	p.AddServiceOf(goioc.Scope,t3)

然后愉快地获取这些对象实例:

	// 构建服务 Provider
	p := collection.Build()

	v1, _ := p.GetService(t1)
	v2, _ := p.GetService(t2)
	v3, _ := p.GetService(t3)

	fmt.Println(*v1)
	fmt.Println(*v2)
	fmt.Println(*v3)

打印对象信息:

&{0x3abdd8}
&{{}}
&{0x3abdd8}

可以看到,当你注入实例后,结构体字段可以是接口、结构体或结构体指针,goioc 会根据不同的情况注入对应的实例。

前面提到了对象是生命周期,这里有些地方需要注意。

如果字段是接口和结构体指针,那么 scope 生命周期时,注入的对象是同一个,可以参考前面的 v1、v3 的 Dog 字段,Dog 字段类型虽然不同,但是因为可以存储指针,因此注入的对象是同一个。如果字段是结构体,由于 Go 语言中结构体是值类型,因此给值类型赋值是,是值赋值,因此对象不是同一个了。

不会自动注入本身

下面是一个依赖注入过程:

	// 获取 reflect.Type
	imy := reflect.TypeOf((*IAnimal)(nil)).Elem()
	my := reflect.TypeOf((*Dog)(nil)).Elem()

	// 创建容器
    sc := &ServiceCollection{}

	// 注入服务,生命周期为 scoped
	sc.AddServiceOf(goioc.Scope,imy, my)

此时,注册的服务是 IAnimal,你只能通过 IAnimal 获取实例,无法通过 Dog 获取实例。

如果你想获取 Dog,需要自行注入:

	// 注入服务,生命周期为 scoped
	p.AddServiceOf(goioc.Scope,imy, my)
	p.AddService(my)

如果是结构体字段,则使用 IAnimal、Dog、*Dog 的形式都可以。

以上就是深入了解Go语言中goioc框架的使用的详细内容,更多关于Go语言 goioc框架的资料请关注我们其它相关文章!

(0)

相关推荐

  • Golang轻量级IoC容器安装使用示例

    目录 1. iocgo简介 2. iocgo如何使用 2.1 iocgo包的安装 2.2 使用示例与说明 2.2.1 最简单的例子: 2.22. Register 的选项 2.2.3. 注册实例 2.2.4. 获得实例 2.2.5. 结构体参数和字段填充 2.2.6. 函数调用 2.3 参考: 3. 总结 1. iocgo简介 习惯于Java或者C#开发的人应该对控制反转与依赖注入应该再熟悉不过了.在Java平台有鼎鼎大名的Spring框架,在C#平台有Autofac,Unity,Windsor

  • 深入了解Go语言中goioc框架的使用

    目录 goioc 介绍 快速上手 接口介绍 使用 goioc 如何使用 生命周期 实例化 获取对象 结构体字段依赖注入 反射形式使用 goioc 如何使用 接口.结构体.结构体指针 不会自动注入本身 goioc 介绍 goioc 是一个基于 GO 语言编写的依赖注入框架,基于反射来进行编写. 支持泛型: 简单易用的 API: 简易版本的对象生命周期管理,作用域内对象具有生命: 延迟加载,在需要的时候才会实例化对象: 支持结构体字段注入,多层注入: 对象实例化线程安全,作用域内只会被执行一次. 下

  • Go 语言中gin使用gzip压缩遇到的问题

    最近学习go语言写了个 成都房地产薪酬 网站,抓取网上的招聘信息并进行统计.中间遇到一些坑在这里记录下来方便以后查阅 gzip压缩是每个web应用必不可少的,这项目使用gin作为web框架,gin支持使用中间件,github上有实现好的gzip中间件:gin-contrib/gzip 安装: go get github.com/gin-contrib/gzip 使用: func main() { r := gin.Default() r.Use(gzip.Gzip(gzip.DefaultCom

  • 详解Go语言中Get/Post请求测试

    目录 gin安装 Get请求测试 Post请求测试 基础语法差不多了,需要开始实践到一下项目,先来web框架gin吧,做一个后端web服务. 在把项目搭建起来的过程中,我也要结合实际的工作经验,补充一些项目结构.开发组件上的理解. 项目地址:github地址 gin安装 先将gin安装一下,安装依赖go语言还是比较方便的. 在安装之前先配置一下goproxy. 命令如下: go env -w GO111MODULE=on go env -w GOPROXY=https://mirrors.ali

  • Go语言中interface语法与使用详解

    目录 初识interface 基本语法 其他注意事项 interface底层实现 iface eface 侵入式与非侵入式的理解 interface的应用场景 类型转换 实现多态功能 补充:interface 与 nil 的比较 总结 初识interface Go语言的面向对象的知识点时,发现它的面向对象能力全靠 interface 撑着,而且它的 interface 还与我们以前知道的 interface 完全不同.故而整个过程不断的思考为什么要如此设计?这样设计给我们带来了什么影响? int

  • 一篇文章搞懂Go语言中的Context

    目录 0 前置知识sync.WaitGroup 1 简介 2 context.Context引入 3 context包的其他常用函数 3.1 context.Background和context.TODO 3.2 context.WithCancel和 3.3 context.WithTimeout 3.4 context.WithDeadline 3.5 context.WithValue 4 实例:请求浏览器超时 5 Context包都在哪些地方使用 6 小结 0 前置知识sync.Wait

  • go语言中GoMock安装使用详解

    目录 安装 开始 使用参数匹配器 经验之谈 安装 GoMock是一个Go框架.它与内置的测试包整合得很好,并在单元测试时提供了灵活性.正如我们所知,对具有外部资源(数据库.网络和文件)或依赖关系的代码进行单元测试总是很麻烦. 为了使用GoMock,我们需要安装gomock包github.com/golang/mock/gomock和mockgen代码生成工具github.com/golang/mock/mockgen.使用这个命令行: go get github.com/golang/mock/

  • ASP中用select case代替其他语言中的switch case, default用case else

    asp中不能用switch语句,要用select case语句了 简单的介绍一下 选择报表的工作一样,如果语句.然而不同的是,他们可以检查多个值.当然,你有 多个相同的,如果.. else语句,但是这并不总是最好的方法. 选择语句允许一个程序来计算表达式,并试图匹配表达式的值案件标签.如果找到匹 配,程序执行相关的声明.对于SELECT语句的语法如下: select case expression case label_1 statements_1 case label_2 statements

  • C语言中printf()缓冲问题详解

    前言 缓冲区又称为缓存,它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区. 为什么要引入缓冲区 比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度. 又比如,

  • C语言中的getchar和putchar的使用方法

    C语言中的getchar和putchar的使用方法 getchar是以行为单位进行存取的. 当用getchar进行输入时,如果输入的第一个字符为有效字符(即输入是文件结束符EOF,Windows下为组合键Ctrl+Z, Unix/Linux下为组合键Ctrl+D),那么只有当最后一个输入字符为换行符'\n'(也可以是文件结束符EOF,EOF将在后面讨论)时, getchar才会停止执行,整个程序将会往下执行.譬如下面程序段: while((c = getchar()) != EOF){ putc

  • 详解C语言中return与exit的区别

    详解C语言中return与exit的区别 1,exit用于在程序运行的过程中随时结束程序,exit的参数是返回给OS的.main函数结束时也会隐式地调用exit函数.exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流.关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件.exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,而return是返回函数值并退出函数 2,return是语言级别的,它

随机推荐