goioc:一个使用 Go 写的简易的 ioc 框架
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)
Dispose 接口
反射形式使用 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
的形式都可以。
goioc:一个使用 Go 写的简易的 ioc 框架的更多相关文章
- 手写一个简易的IOC
这个小项目是我读过一点Spring的源码后,模仿Spring的IOC写的一个简易的IOC,当然Spring的在天上,我写的在马里亚纳海沟,哈哈 感兴趣的小伙伴可以去我的github拉取代码看着玩 地址 ...
- Summer——从头开始写一个简易的Spring框架
Summer--从头开始写一个简易的Spring框架 参考Spring框架实现一个简易类似的Java框架.计划陆续实现IOC.AOP.以及数据访问模块和事务控制模块. ...
- 我手写的简易tomcat
前述 自己手写的简易的tomcat,实现了tomcat的基本响应功能,项目代码已经上传到我的Github,刚刚开始学习这里,当前还存在很多问题 项目简述及代码 当我们的Web运行的时候,从浏览器发出的 ...
- 从零实现一个简易的jQuery框架之二—核心思路详解
如何读源码 jQuery整体框架甚是复杂,也不易读懂.但是若想要在前端的路上走得更远.更好,研究分析前端的框架无疑是进阶路上必经之路.但是庞大的源码往往让我们不知道从何处开始下手.在很长的时间里我也被 ...
- 手写Promise简易版
话不多说,直接上代码 通过ES5的模块化封装,向外暴露一个属性 (function(window){ const PENDING = 'pending'; const RESOLVED = 'fulf ...
- php调用一个c语言写的接口问题
用php调用一个c语言写的soap接口时,遇到一个问题:不管提交的数据正确与否,都无法请求到接口 1.用php标准的soap接口去请求 2.拼接xml数据去请求 以上两种方式都不正确 解决办法:php ...
- 只是一个用EF写的一个简单的分页方法而已
只是一个用EF写的一个简单的分页方法而已 慢慢的写吧.比如,第一步,先把所有数据查询出来吧. //第一步. public IQueryable<UserInfo> LoadPagesFor ...
- 一个用C++写的Json解析与处理库
什么是Json?这个库能做什么? JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is e ...
- 【Android开发经验】来,咱们自己写一个Android的IOC框架!
到眼下位置.afinal开发框架也是用了好几个月了,还记得第一次使用凝视完毕控件的初始化和事件绑定的时候,当时的心情是多么的兴奋- -代码居然能够这样写!然后随着不断的学习,也慢慢的对IOC框架和注解 ...
- 干净win7要做几步才能运行第一个Spring MVC 写的动态web程序
干净win7要做几步才能运行第一个Spring MVC 写的动态web程序: 1. 下载安装jdk 2. 配置Java环境变量 3. 测试一下第1,2两步是否完全成功:http://jingyan.b ...
随机推荐
- HK32F030MF4P6的Linux GCC工具链和VSCode开发环境
HK32F030MF4P6简介 航顺的 HK32F030MF4P6, TSSOP20封装, Arm Cortex M0 内核, 内建32MHz时钟, 16K Flash, 2K RAM(实际上可用的有 ...
- JAVA SE 基础总结
§ 基础知识 一.程序组织与运行原理 1.1 程序组织 一个 JAVA 程序文件中主要由如下几部分构成: package 声明 public 类:public 类与类文件名相同,因为其是作为该类文件唯 ...
- Beats:在Docker里运行Filebeat
- linux系统排查数据包常用命令
1.查看当前系统中生效的所有参数 sysctl -a 2.统计处于TIME_WAIT状态的TCP连接数 netstat -ant|grep TIME_WAIT|wc -l 3.统计TCP连接数 net ...
- centos离线安装nvm
PS:因为项目需,客户现场不能联网需要不同的node版本来切换,里面已经内置好了node 8.11.2和12.1.0 两个版本,使用nvm可以切换 链接:https://pan.baidu.com/s ...
- 一个终端工具竟然有AI功能?使用了1天我立马把其他终端全卸载了!太香了!
前言 平常工作需要频繁使用终端工具,有一个好的命令行终端工具是非常重要的. 尤其是使用mac的小伙伴,估计不少人都觉得iterm2才是最好的终端工具. 其实起初我也是这么觉得的,但是最近直到我使用了这 ...
- 洛谷P1950 长方形(单调栈)
一道单调栈的好题啊...... 思路是很奇妙的,对于每个点(i,j),我们可以算它对答案的贡献(即包含它的矩形数量),包含该点的矩形,点的高度为h[j],点右边的高度一定大于等于h[j],左边的高度一 ...
- 关于多个 Kubernetes 集群指标的采集操作
简介 在使用观测云期间,有时需要针对一个工作空间接入多个 Kubernetes 集群指标,通过观测云提供的全局 Tag 的方式来进行区分,大大提高了效率.下面是我总结的操作步骤. 当集群中只有一个采集 ...
- jsp页面中怎么利用a标签的href进行传递参数以及需要注意的地方
jsp页面中: <a href="${pageContext.request.contextPath }/infoController/getProductInfo?productId ...
- 2.RabbitMQ系列之生产者
1. 新建队列 2. 新增POM.xml配置文件 <parent> <groupId>org.springframework.boot</groupId> < ...