• 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 的形式都可以。

  • 相关阅读:
    Jenkins+vue发布项目
    力扣学习记录(每日更新)
    Allegro如何缩放数据操作指导
    源码必须会丨一个bug的解决过程,让你明白阅读源码的重要性!
    【C++】运算符与表达式(学习笔记)
    单片机——Keil uVision4新建工程
    什么是DNS智能云解析,什么是NS,更换NS多久生效?
    CentOS7.9中使用packstack安装train版本
    基于离散、连续、线性和非线性模型进行模型预测(MPC)控制(Matlab代码实现)
    操作教程|如何注册成为Moonbeam社区代表参与治理
  • 原文地址:https://www.cnblogs.com/whuanle/p/16930341.html