最近做项目中,天生 工{具}[照样使用对照原始的New和简朴工厂的方式,使用过程中感受不太爽直(依赖慎密,有点改动就对照贫苦),照样对照喜欢使用依赖注入的方式。

然后网上没有找到对照好用的依赖注入包,就自己着手写了一个,也不要求啥,能用就会, 把[我从繁琐的New方式中解脱出来。

<先说一下简朴>‘实现’原理

  1. 通过反射读取 工{具}[的[依赖(golang是通过tag‘实现’)
  2. 在容器中查找有无该 工{具}[实例
  3. ‘如果有该 工{具}[实例或’者建立 工{具}[的工厂方式,则注入 工{具}[或使用工厂建立 工{具}[并注入
  4. 如果无该 工{具}[实例,则报错

需要注重的地方:

1、注入的 工{具}[首字母需要大写,小写的话,在go‘中代’表私有,通过反射无法修改值

2、go反射无法通过读取配置文件信息动态建立 工{具}[

 

「首先」,先容一下项目条理结构

 

 

  主要解决[:数据库-》仓储(读写星散)-》服务-》控制器 这几层的依赖注入问题

数据库,我这里为了简化数据库<细节>,接纳模拟数据的设施来‘实现’,现实项目中是需要读取「真是数据库的」,代码如下

//准备用户数据,现实开发一样平常从数据库读取
var users []entities.UserEntity

func init() {
    users = append(users, entities.UserEntity{ID: 1, Name: "小明", NickName: "无敌", Gender: 1, Age: 13, Tel: "18886588086", Address: "【中国】,广东,『深圳』"})
    users = append(users, entities.UserEntity{ID: 2, Name: "小红", NickName: "傻妞", Gender: 0, Age: 13, Tel: "1888658809", Address: "【中国】,广东,广州"})
}

type MockDB struct {
    Host  string
    User  string
    Pwd   string
    Alias string
}

func (db *MockDB) Connect() bool {
    return true
}

func (db *MockDB) Users() []entities.UserEntity {
    return users
}

func (db *MockDB) Close() {

}

数据仓储,为了‘实现’读写星散,星散了两个接口,例如user仓储分为i_user_reader和i_user_repository,其中i_user_repository包罗i_user_reader(即继续了i_user_reader)

接口界说如下:

type IUserReader interface {
    GetUsers() []dtos.UserDto
    GetUser(id int64) *dtos.UserDto
    GetMaxUserId() int64
}

type IUserRepository interface {
    IUserReader
    AddUser(user *inputs.UserInput) error
    UpdateUserNickName(id int64, nickName string) error
}

仓储‘实现’如下:

user_read

type UserRead struct {
    ReadDb *db.MockDB `inject:"MockDBRead"`
}

func (r *UserRead) GetUsers() []dtos.UserDto {
    if r.ReadDb.Connect() {
        users := r.ReadDb.Users()
        var list []dtos.UserDto
        for _, user := range users {
            list = append(list, dtos.UserDto{ID: user.ID, Name: user.Name, NickName: user.NickName, Gender: user.Gender, Age: user.Age, Tel: user.Tel, Address: user.Address})
        }
        return list
    }
    return nil
}

func (r *UserRead) GetUser(id int64) *dtos.UserDto {
    if r.ReadDb.Connect() {
        users := r.ReadDb.Users()
        for _, user := range users {
            if user.ID == id {
                return &dtos.UserDto{ID: user.ID, Name: user.Name, NickName: user.NickName, Gender: user.Gender, Age: user.Age, Tel: user.Tel, Address: user.Address}
            }
        }
        return &dtos.UserDto{}
    }
    return nil
}

func (r *UserRead) GetMaxUserId() int64 {
    var maxId int64
    if r.ReadDb.Connect() {
        users := r.ReadDb.Users()
        for _, user := range users {
            if user.ID > maxId {
                maxId = user.ID
            }
        }
    }
    return maxId
}
UserRepository:
type UserRepository struct {
    UserRead
    WriteDb *db.MockDB `inject:"MockDBWrite"`
}

func (w *UserRepository) AddUser(user *inputs.UserInput) error {
    model := entities.UserEntity{}
    model.ID = w.GetMaxUserId() + 1
    model.Name = user.Name
    model.NickName = user.NickName
    model.Gender = user.Gender
    model.Age = user.Age
    model.Address = user.Address
    if w.ReadDb.Connect() {
        users := w.ReadDb.Users()
        users = append(users, model)
    }
    return nil
}

func (w *UserRepository) UpdateUserNickName(id int64, nickName string) error {
    user := w.GetUser(id)
    if user.ID > 0 {
        user.NickName = nickName
        return nil
    } else {
        return errors.New("(未找到用户)信息")
    }
}

注重,user_read 依赖注入的是读[db:ReadDB,user_repository依赖注入的是写db:WriteDB

 

服务的接口和‘实现’

i_user_service:

type IUserService interface {
    GetUsers() []dtos.UserDto
    GetUser(id int64) *dtos.UserDto
    AddUser(user *inputs.UserInput) error
}

user_service:

type UserService struct {
    UserRepository repositories.IUserRepository `inject:"UserRepository"`
}

func (s *UserService) AddUser(user *inputs.UserInput) error {
    return s.UserRepository.AddUser(user)
}

func (s *UserService) GetUsers() []dtos.UserDto {
    return s.UserRepository.GetUsers()
}

func (s *UserService) GetUser(id int64) *dtos.UserDto {
    return s.UserRepository.GetUser(id)
}

UserService依赖注入UserRepository,另外,项目中,【特意把】仓储接口界说和服务放在统一层,是为了让服务只依赖仓储接口,不依赖仓储详细‘实现’。这算是设计模式原则的依赖倒置原则的体现吧。

控制器‘实现’:

type UserController struct {
    UserService user.IUserService `inject:"UserService"`
}

func (ctrl *UserController) GetUsers(ctx *gin.Context) {
    users := ctrl.UserService.GetUsers()
    Ok(Response{Code: Success, Msg: "获取用户乐成!", Data: users}, ctx)
}

func (ctrl *UserController) GetUser(ctx *gin.Context) {
    idStr := ctx.Param("id")
    id, err := strconv.ParseInt(idStr, 10, 64)
    if err != nil {
        BadRequestError("id参数花样错误", ctx)
        return
    }
    users := ctrl.UserService.GetUser(id)
    Ok(Response{Code: Success, Msg: "获取用户乐成!", Data: users}, ctx)
}

func (ctrl *UserController) AddUser(ctx *gin.Context) {
    input := inputs.UserInput{}
    err := ctx.ShouldBindJSON(&input)
    if err != nil {
        BadRequestError("参数错误", ctx)
        return
    }
    err = ctrl.UserService.AddUser(&input)
    if err != nil {
        Ok(Response{Code: Failed, Msg: err.Error()}, ctx)
        return
    }
    Ok(Response{Code: Success, Msg: "添加用户乐成!"}, ctx)
}

UserController依赖注入UserService

接下来是‘实现’依赖注入「的焦点代码」,容器的‘实现’

Container:

var injectTagName = "inject" //依赖注入tag名

//生命周期
// singleton:单例 单一实例,每次使用都是该实例
// transient:瞬时实例,每次使用都建立新的实例
type Container struct {
    sync.Mutex
    singletons map[string]interface{}
    transients map[string]factory
}

type factory = func() (interface{}, error)

//注册单例 工{具}[
func (c *Container) SetSingleton(name string, singleton interface{}) {
    c.Lock()
    c.singletons[name] = singleton
    c.Unlock()
}

func (c *Container) GetSingleton(name string) interface{} {
    return c.singletons[name]
}

//注册瞬时实例建立工厂方式
func (c *Container) SetTransient(name string, factory factory) {
    c.Lock()
    c.transients[name] = factory
    c.Unlock()
}

func (c *Container) GetTransient(name string) interface{} {
    factory := c.transients[name]
    instance, _ := factory()
    return instance
}

//注入实例
func (c *Container) Entry(instance interface{}) error {
    err := c.entryValue(reflect.ValueOf(instance))
    if err != nil {
        return err
    }
    return nil
}

func (c *Container) entryValue(value reflect.Value) error {
    if value.Kind() != reflect.Ptr {
        return errors.New("必须为指针")
    }
    elemType, elemValue := value.Type().Elem(), value.Elem()
    for i := 0; i < elemType.NumField(); i++ {
        if !elemValue.Field(i).CanSet() { //不能设置 跳过
            continue
        }

        fieldType := elemType.Field(i)
        if fieldType.Anonymous {
            //fmt.Println(fieldType.Name + "是匿名字段")
            item := reflect.New(elemValue.Field(i).Type())
            c.entryValue(item) //『递归』注入
            elemValue.Field(i).Set(item.Elem())
        } else {
            if elemValue.Field(i).IsZero() { //零值才注入
                //fmt.Println(elemValue.Field(i).Interface())
                //fmt.Println(fieldType.Name)
                tag := fieldType.Tag.Get(injectTagName)
                injectInstance, err := c.getInstance(tag)
                if err != nil {
                    return err
                }
                c.entryValue(reflect.ValueOf(injectInstance)) //『递归』注入

                elemValue.Field(i).Set(reflect.ValueOf(injectInstance))
            } else {
                fmt.Println(fieldType.Name)
            }
        }
    }
    return nil
}

func (c *Container) getInstance(tag string) (interface{}, error) {
    var injectName string
    tags := strings.Split(tag, ",")
    if len(tags) == 0 {
        injectName = ""
    } else {
        injectName = tags[0]
    }

    if c.isTransient(tag) {
        factory, ok := c.transients[injectName]
        if !ok {
            return nil, errors.New("transient factory not found")
        } else {
            return factory()
        }
    } else { //默认单例
        instance, ok := c.singletons[injectName]
        if !ok || instance == nil {
            return nil, errors.New(injectName + " dependency not found")
        } else {
            return instance, nil
        }
    }
}

// transient:瞬时实例,每次使用都建立新的实例
func (c *Container) isTransient(tag string) bool {
    tags := strings.Split(tag, ",")
    for _, name := range tags {
        if name == "transient" {
            return true
        }
    }
    return false
}

func (c *Container) String() string {
    lines := make([]string, 0, len(c.singletons)+len(c.transients)+2)
    lines = append(lines, "singletons:")
    for key, value := range c.singletons {
        line := fmt.Sprintf("    %s: %x %s", key, c.singletons[key], reflect.TypeOf(value).String())
        lines = append(lines, line)
    }

    lines = append(lines, "transients:")
    for key, value := range c.transients {
        line := fmt.Sprintf("    %s: %x %s", key, c.transients[key], reflect.TypeOf(value).String())
        lines = append(lines, line)
    }
    return strings.Join(lines, "\n")
}

{这里使用了两种}生命周期的实例:单例和瞬时(其他生命周期,水平有限哈)

简朴说下原理,“容器主要”包罗两个map 工{具}[,用来存储 工{具}[和建立对方方式,然后依赖注入‘实现’,就是通过反射获取tag信息,再去容器map中获取 工{具}[,《通过反射把获》取的 工{具}[赋值到字段中。

我这里接纳了『递归』注入的方式,以是本项目中,只用注入UserController 工{具}[即可,由于现实项目中多点是有多个Controller 工{具}[,以是我这里使用了个简《朴工厂来建立》Controller 工{具}[,然后只用注入工厂方式即可

工厂方式‘实现’如下:

type CtrlFactory struct {
    UserCtrl *controllers.UserController `inject:"UserController"`
}

使用容器前,需要先初始化好容器 工{具}[,这里使用一个全局 工{具}[,【然后初始化好】需要注入的 工{具}[,‘实现’代码如下:

var GContainer = &Container{
    singletons: make(map[string]interface{}),
    transients: make(map[string]factory),
}

func Init() {
    //db
    GContainer.SetSingleton("MockDBRead", &db.MockDB{Host: "192.168.1.12:3036", User: "root", Pwd: "123456", Alias: "Read"})
    GContainer.SetSingleton("MockDBWrite", &db.MockDB{Host: "192.168.1.25:3036", User: "root", Pwd: "123456", Alias: "Write"})

    //仓储
    GContainer.SetSingleton("UserRepository", &user.UserRepository{})

    //服务
    GContainer.SetSingleton("UserService", &userDomain.UserService{})

    //控制器
    GContainer.SetSingleton("UserController", &controllers.UserController{})

    //控制器工厂
    ctlFactory := &CtrlFactory{}
    GContainer.SetSingleton("CtrlFactory", ctlFactory)

    GContainer.Entry(ctlFactory) //注入

    fmt.Println(GContainer.String())
}

依赖注入代码‘实现’讲完了,然后就是详细使用了,使用时,先在main〖方式中挪用容器〗失事化方式Init() (注重,这里Init特意大写,『要和』go<包>的init区分,go<包>的init(是自动挪)用,这里大写的Init是需要手动挪用的,至于为啥呢,‘注’重是可以控制挪用时机,go<包>的init挪用顺序有点莫名其妙,特别是包引用庞大的时‘刻’),main代码如下:

func main() {
    Init()
    Run()
}

func Init() {
    inject.Init()
}

func Run() {
    router := router.Init()

    s := &http.Server{
        Addr:           ":8080",
        Handler:        router,
        ReadTimeout:    time.Duration(10) * time.Second,
        WriteTimeout:   time.Duration(10) * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    go func() {
        log.Println("Server Listen at:8080")
        if err := s.ListenAndServe(); err != nil {
            log.Printf("Listen:%s\n", err)
        }
    }()

    quit := make(chan os.Signal)
    signal.Notify(quit, os.Interrupt)
    <-quit

    log.Println("Shutdown Server...")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := s.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }
    log.Println("Server exiting")
}

我这里使用了gin框架来构建http服务

初始化话完毕后,就是在路由中使用controller了,先从容器中获取工厂 工{具}[,然后通过go类型推断转化为详细类型,代码如下:

func Init() *gin.Engine {
    // Creates a router without any middleware by default
    r := gin.New()
    r.Use(gin.Logger())
    // Recovery middleware recovers from any panics and writes a 500 if there was one.
    r.Use(gin.Recovery())

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    factory := inject.GContainer.GetSingleton("CtrlFactory")
    ctrlFactory := factory.(*inject.CtrlFactory)

    apiV1 := r.Group("/api/v1")
    //users
    userRg := apiV1.Group("/user")
    {
        userRg.POST("", ctrlFactory.UserCtrl.AddUser)
        userRg.GET("", ctrlFactory.UserCtrl.GetUsers)
        userRg.GET("/:id", ctrlFactory.UserCtrl.GetUser)
    }

    gin.SetMode("debug")
    return r
}

焦点代码就是:

factory := inject.GContainer.GetSingleton("CtrlFactory")
ctrlFactory := factory.(*inject.CtrlFactory)

ok,先容完了。初始弄这个依赖注入可能以为有点贫苦,但这是一劳永逸的设施,后面有啥增添修改的就「对照简朴」

详细代码放在github上了,有兴趣可以关注一下:https://github.com/marshhu/ma-inject

 

,

Allbet

www.dfxwy36.com欢迎进入欧博开户平台(Allbet Gaming),欧博开户平台开放欧博(Allbet)开户、欧博(Allbet)代理开户、欧博(Allbet)电脑客户端、欧博(Allbet)APP下载等业务

Allbet Gaming声明:该文看法仅代表作者自己,与阳光在线无关。转载请注明:【欧博】客户端:go{语言}依赖注入实现
发布评论

分享到:

聊城信息港:金色前哨 | Reddit〖区块链积分〗系统细节曝光:将在以太坊上运行
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。