go语言依赖注入实现
最近做项目中,生成对象还是使用比较原始的New和简单工厂的方式,使用过程中感觉不太爽快(依赖紧密,有点改动就比较麻烦),还是比较喜欢使用依赖注入的方式。
然后网上没有找到比较好用的依赖注入包,就自己动手写了一个,也不要求啥,能用就会,把我从繁琐的New方法中解脱出来。
先说一下简单实现原理
- 通过反射读取对象的依赖(golang是通过tag实现)
- 在容器中查找有无该对象实例
- 如果有该对象实例或者创建对象的工厂方法,则注入对象或使用工厂创建对象并注入
- 如果无该对象实例,则报错
需要注意的地方:
1、注入的对象首字母需要大写,小写的话,在go中代表私有,通过反射无法修改值
2、go反射无法通过读取配置文件信息动态创建对象
首先,介绍一下项目层次结构
主要解决:数据库-》仓储(读写分离)-》服务-》控制器 这几层的依赖注入问题
数据库,我这里为了简化数据库细节,采用模拟数据的办法来实现,实际项目中是需要读取真是数据库的,代码如下
//准备用户数据,实际开发一般从数据库读取
var users []entities.UserEntity func init() {
users = append(users, entities.UserEntity{ID: , Name: "小明", NickName: "无敌", Gender: , Age: , Tel: "", Address: "中国,广东,深圳"})
users = append(users, entities.UserEntity{ID: , Name: "小红", NickName: "傻妞", Gender: , Age: , Tel: "", 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() +
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 > {
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, , )
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 := ; 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) == {
injectName = ""
} else {
injectName = tags[]
} 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, , len(c.singletons)+len(c.transients)+)
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: "", Alias: "Read"})
GContainer.SetSingleton("MockDBWrite", &db.MockDB{Host: "192.168.1.25:3036", User: "root", Pwd: "", 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() * time.Second,
WriteTimeout: time.Duration() * time.Second,
MaxHeaderBytes: << ,
}
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(), *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(, 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
go语言依赖注入实现的更多相关文章
- 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- 通过中看不中用的代码分析Ioc容器,依赖注入....
/** * 通过生产拥有超能力的超人实例 来理解IOC容器 */ //超能力模组接口 interface SuperModuleInterface{ public function activate( ...
- 30行代码让你理解angular依赖注入:angular 依赖注入原理
依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖 ...
- Spring系列之依赖注入的方式
一.依赖注入方式 对于spring配置一个bean时,如果需要给该bean提供一些初始化参数,则需要通过依赖注入方式,所谓的依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象 ...
- angular 依赖注入原理
依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖 ...
- C# 依赖注入
http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html 这篇文章真的非常非常好···绝对值得收藏学习. 目录 目录 1 ...
- Hello Spring Framework——依赖注入(DI)与控制翻转(IoC)
又到年关了,还有几天就是春节.趁最后还有些时间,复习一下Spring的官方文档. 写在前面的话: Spring是我首次开始尝试通过官方文档来学习的框架(以前学习Struts和Hibernate都大多是 ...
- 控制反转容器& 依赖注入模式 ---读感。
几个web框架 : sprint Avalon PicoContainerclass MovieLister MovieFinder finder = ServiceLocator.movieFind ...
- 细数Javascript技术栈中的四种依赖注入
作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...
随机推荐
- Linux打开文件句柄/proc/sys/fs/file-max和ulimit -n的区别
max-file 表示系统级别的能够打开的文件句柄的数量.是对整个系统的限制,并不是针对用户的.ulimit -n 控制进程级别能够打开的文件句柄的数量.提供对shell及其启动的进程的可用文件句柄的 ...
- Redis(一):数据结构与对象
前言 本书是Redis设计与实现的读书笔记,旨在对Redis底层的数据结构及实现有一定了解.本书所有的代码基于Redis 3.0. 简单动态字符串 SDS Redis没有直接使用C语言中的字符串,而是 ...
- LinearLayout控件
LinearLayout是线性布局控件,它包含的子控件将以横向或竖向的方式排列,按照相对位置来排列所有的widgets或者其他的containers,超过边界时,某些控件将缺失或消失.因此一个垂直列表 ...
- 学数据库你竟然不用用JAVA写代码,可惜你遇到了我! JAVA连接数据库(JDBC)的安装使用教程
Step 1 你得有Eclipse 没有出门右拐,我教不了你. Step 2 你得有Mysql MySQL的详细安装过程,我在另一篇博客中给出.戳我 Step 3 安装JDBC 可以去官网下,如果用的 ...
- JSP、ASP、PHP Web应用程序怎么这么多P!
之前我们说完了计算机网络应用程序的两种结构:C/S,B/S(传送门)今天我们详细说一说B/S开发中的这么多P是干什么的. 1.什么是Web应用程序 一个Web应用程序是由完成特定任务的各种Web组件( ...
- Web 跨域请求问题的解决方案- CORS 方案
1.什么是跨域 跨域是指跨域名的访问,以下情况都属于跨域: 跨域现象 实例 域名不相同 www.baidu.com与www.taobao 一级域名相同,但是端口不相同 www.baidu.com:80 ...
- 内存迟迟下不去,可能你就差一个GC.Collect
一:背景 1. 讲故事 我们有一家top级的淘品牌店铺,为了后续的加速计算,在程序启动的时候灌入她家的核心数据到内存中,灌入完成后内存高达100G,虽然云上的机器内存有256G,然被这么划掉一半看着还 ...
- Linova and Kingdom(树型-贪心)
题目大意:给定一棵树,1为首都(首都可以是工业城市也可以是旅游城市),一共有n个点. 其中要选出k个工业城市,每个工业城市出一个代表去首都,其快乐值是其途径旅游城市(非工业)的个数 求所有快乐值相加的 ...
- Xenia and Colorful Gems(二分--思维)
给定三个数组a,b,c. 要求从每个数字取一个数,使得两两之差和最小. 求出这个数. \(我又懵逼了.我是会O(n^3)的暴力啊,怎么办.\) \(\color{Red}{从结果看,选出来的三个数必定 ...
- 网络流 A - PIGS POJ - 1149 最大流
A - PIGS POJ - 1149 这个题目我开始感觉很难,然后去看了一份题解,写的很好 https://wenku.baidu.com/view/0ad00abec77da26925c5b01c ...