feat: 新增简易版ioc

This commit is contained in:
meilin.huang
2024-01-21 22:52:20 +08:00
parent f4a64b96a9
commit f27d3d200f
106 changed files with 815 additions and 707 deletions

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.7.0"
Version = "v1.7.1"
)
func GetAppInfo() string {

View File

@@ -0,0 +1,28 @@
package ioc
type ComponentOption func(component *Component)
// 组件名
func WithComponentName(name string) ComponentOption {
return func(c *Component) {
c.Name = name
}
}
// 组件
type Component struct {
Name string // 组件名
Value any // 组件实例
}
func NewComponent(val any, opts ...ComponentOption) *Component {
component := &Component{
Value: val,
}
for _, o := range opts {
o(component)
}
return component
}

20
server/pkg/ioc/default.go Normal file
View File

@@ -0,0 +1,20 @@
package ioc
// 全局默认实例容器
var DefaultContainer = NewContainer()
// 注册实例至全局默认ioc容器
func Register(component any, opts ...ComponentOption) {
DefaultContainer.Register(component, opts...)
}
// 根据组件名从全局默认ioc容器获取实例
func Get[T any](name string) T {
c, _ := DefaultContainer.Get(name)
return c.(T)
}
// 使用全局默认ioc容器中已注册的组件实例 -> 注入到指定实例所依赖的组件实例
func Inject(component any) error {
return DefaultContainer.Inject(component)
}

159
server/pkg/ioc/ioc.go Normal file
View File

@@ -0,0 +1,159 @@
package ioc
import (
"errors"
"fmt"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/structx"
"reflect"
"strings"
)
// 容器
type Container struct {
components map[string]*Component
}
func NewContainer() *Container {
return &Container{
components: make(map[string]*Component),
}
}
// 注册实例至实例容器
func (c *Container) Register(bean any, opts ...ComponentOption) {
component := NewComponent(bean, opts...)
componentName := component.Name
cType := structx.IndirectType(reflect.TypeOf(component.Value))
// 组件名为空,则去组件类型名称作为组件名
if componentName == "" {
componentName := cType.Name()
component.Name = componentName
}
if _, err := c.Get(componentName); err == nil {
logx.Warnf("组件名[%s]已经注册至容器, 重复注册...", componentName)
}
logx.Debugf("ioc register : %s = %s.%s", componentName, cType.PkgPath(), cType.Name())
c.components[componentName] = component
}
// 注册对象实例的字段含有inject:"xxx"标签或者Setter方法则注入对应组件实例
func (c *Container) Inject(obj any) error {
objValue := reflect.ValueOf(obj)
if err := c.injectWithField(objValue); err != nil {
return err
}
if err := c.injectWithMethod(objValue); err != nil {
return err
}
return nil
}
// 对所有组件实例执行Inject。即为实例字段注入依赖的组件实例
func (c *Container) InjectComponents() error {
for _, v := range c.components {
if err := c.Inject(v.Value); err != nil {
return err
}
}
return nil
}
// 根据组件实例名,获取对应实例信息
func (c *Container) Get(name string) (any, error) {
component, ok := c.components[name]
if !ok {
return nil, errors.New("component not found: " + name)
}
return component.Value, nil
}
// 根据实例字段的inject:"xxx"标签进行依赖注入
func (c *Container) injectWithField(objValue reflect.Value) error {
objValue = structx.Indirect(objValue)
objType := objValue.Type()
for i := 0; i < objType.NumField(); i++ {
field := objType.Field(i)
componentName, ok := field.Tag.Lookup("inject")
if !ok {
continue
}
// inject tag字段名为空则默认为字段名
if componentName == "" {
componentName = field.Name
}
injectInfo := fmt.Sprintf("ioc field inject [%s -> %s.%s#%s]", componentName, objType.PkgPath(), objType.Name(), field.Name)
logx.Debugf(injectInfo)
component, err := c.Get(componentName)
if err != nil {
return fmt.Errorf("%s error: %s", injectInfo, err.Error())
}
// 判断字段类型与需要注入的组件类型是否为可赋值关系
componentType := reflect.TypeOf(component)
if !componentType.AssignableTo(field.Type) {
componentType = structx.IndirectType(componentType)
return fmt.Errorf("%s error: 注入类型不一致(期望类型->%s.%s, 组件类型->%s.%s)", injectInfo, field.Type.PkgPath(), field.Type.Name(), componentType.PkgPath(), componentType.Name())
}
objValueField := objValue.Field(i)
if !objValueField.IsValid() || !objValueField.CanSet() {
return fmt.Errorf("%s error: 字段无效或为不可导出类型", injectInfo)
}
objValueField.Set(reflect.ValueOf(component))
}
return nil
}
// 根据实例的Inject方法进行依赖注入
func (c *Container) injectWithMethod(objValue reflect.Value) error {
objType := objValue.Type()
for i := 0; i < objType.NumMethod(); i++ {
method := objType.Method(i)
methodName := method.Name
// 不是以Inject开头的函数则默认跳过
if !strings.HasPrefix(methodName, "Inject") {
continue
}
// 获取组件名InjectTestApp -> TestApp
componentName := methodName[6:]
injectInfo := fmt.Sprintf("ioc method inject [%s.%s#%s(%s)]", objType.Elem().PkgPath(), objType.Elem().Name(), methodName, componentName)
logx.Debugf(injectInfo)
if method.Type.NumIn() != 2 {
logx.Warnf("%s error: 方法入参不为1个, 无法进行注入", injectInfo)
continue
}
component, err := c.Get(componentName)
if err != nil {
return fmt.Errorf("%s error: %s", injectInfo, err.Error())
}
componentType := reflect.TypeOf(component)
// 期望的组件类型,即参数入参类型
expectedComponentType := method.Type.In(1)
if !componentType.AssignableTo(expectedComponentType) {
componentType = structx.IndirectType(componentType)
return fmt.Errorf("%s error: 注入类型不一致(期望类型->%s.%s, 组件类型->%s.%s)", injectInfo, expectedComponentType.PkgPath(), expectedComponentType.Name(), componentType.PkgPath(), componentType.Name())
}
method.Func.Call([]reflect.Value{objValue, reflect.ValueOf(component)})
}
return nil
}