go dig学习笔记

  1. IOC、DI介绍

IOCDI这两词相信对于过去接触过大名鼎鼎的Spring的小伙伴来说应该并不陌生,但很多人往往不能第一时间说出这两个词的主要意思,然后对于一些没有Spring使用经验的人来说,可能就是显得比较陌生。这里简单的介绍一下这两个词的意思。

IOC(Inversion of Control):中文为 “反转控制” ,是一种将设计好的对象交给第三方(IOC容器)来控制设计思想。不同于传统的由程序员来控制对象生命周期的方式,而是由IOC容器来完成对象生命周期的控制。

DI(Dependency Injection):中文为 “依赖注入” ,在创建某一对象时,可能会依赖一些其它的对象,传统方式一般是先创建出依赖对象,然后才能创建出目标对象。当使用了IOC容器后,则当我们获取目标对象时,IOC容器会为我们自动注入依赖对象,不再需要我们手动去创建。

2. Dig介绍

Dig 是Uber开源的一个golang的轻量Ioc库,其主要的实现技术为反射,相对于wirego-Spring比较易用。

3. Dig使用

注:以下内容搬运于<dig package - go.uber.org/dig - Go Packages>,感兴趣的小伙伴可以直接访问原文。

3.1 创建容器

使用以下方法创建一个dig容器实例。

c := dig.New()

3.2 Provide

Provide方法用于注册对象的构造方法,传入参数是一个函数,这个函数的形参列表就是需要dig容器注入的对象(依赖对象)。dig容器会根据传入的函数,在需要时为我们创建出函数的返回值对象(目标对象),并讲返回值放入dig容器中。

err := c.Provide(func(conn *sql.DB) (*UserGateway, error) {
  // ...
})
if err != nil {
  // ...
}

if err := c.Provide(newDBConnection); err != nil {
  // ...
}

对于每一个构造方法的返回值,dig只会创建出一个实例(单例模式)。

err := c.Provide(func(conn *sql.DB) *CommentGateway {
  // ...
})
if err != nil {
  // ...
}

构造方法可以定义任意个形参,并且可以返回错误。

err := c.Provide(func(u *UserGateway, c *CommentGateway) (*RequestHandler, error) {
  // ...
})
if err != nil {
  // ...
}

if err := c.Provide(newHTTPServer); err != nil {
  // ...
}

构造方法也可以定义任意个返回值,并将这些返回值加入到dig容器中。

err := c.Provide(func(conn *sql.DB) (*UserGateway, *CommentGateway, error) {
  // ...
})
if err != nil {
  // ...
}

Provide方法会忽略掉可变参数的形参。下面这段代码,

func NewVoteGateway(db *sql.DB, options ...Option) *VoteGateway

相当于

func NewVoteGateway(db *sql.DB) *VoteGateway

3.3 Invoke

使用Invoke方法来使用添加到容器的类型Invoke的参数是一个函数,这个函数的形参可以是一个或多个,并且可以选择返回错误。dig容器会实例话这个方法形参列表中的实例,如果容器中没有管理形参中对象的构造方法或实例的话则会调用失败。

err := c.Invoke(func(l *log.Logger) {
  // ...
})
if err != nil {
  // ...
}

err := c.Invoke(func(server *http.Server) error {
  // ...
})
if err != nil {
  // ...
}

在传入函数返回的错误会返回给Invoke方法的调用者。

3.4 Parameter Objects(参数对象)

当构造方法的形参列表比较长时,也就是依赖的对象比较多时,会导致代码可读性变差。

func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
  // ...
}

这时我们可以建一个参数对象,并将方法的形参列表替换成这个参数对象来优化代码的可读性。参数对象需要内嵌dig.In结构体,如下所示:

type HandlerParams struct {
  dig.In

  Users    *UserGateway
  Comments *CommentGateway
  Posts    *PostGateway
  Votes    *VoteGateway
  AuthZ    *AuthZGateway
}

func NewHandler(p HandlerParams) *Handler {
  // ...
}

dig可以处理参数对象和参数的各种组合。

func NewHandler(p HandlerParams, l *log.Logger) *Handler {
  // ...
}

3.5 Result objects(返回对象)

当传入函数的返回值的数量太多,也一样会破坏代码的可读性。

func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
  // ...
}

这时可以在一个新的结构体中内嵌dig.Out,来构造一个返回对象,用于替换这些过多的返回值。

type Gateways struct {
  dig.Out

  Users    *UserGateway
  Comments *CommentGateway
  Posts    *PostGateway
}

func SetupGateways(conn *sql.DB) (Gateways, error) {
  // ...
}

3.6 Optional Dependencies(可选依赖)

一些情况下,目标对象依赖的一些对象,可能是可有可无的,就算没有这些依赖对象,目前对象也能以一种服务降级的状态运行。可以在内嵌了dig.In结构体中,在可选依赖字段后面增加optional:"true"的tag。

type UserGatewayParams struct {
  dig.In

  Conn  *sql.DB
  Cache *redis.Client `optional:"true"`
}

当可选依赖不可用时,dig会将这个字段置为0值。

func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
  if p.Cache == nil {
    log.Print("Logging disabled")
  }
  // ...
}

注意:使用了可选参数必须在目标对象的结构体方法中指定可选参数不存在时的逻辑。

3.7 Named Values(命名值)

一些目标对象会用到相同类型的依赖对象,但由于默认dig对相同对象是只保存一个实例的,这时可以通过命名值来允许添加多个相同类型的对象到dig容器中。

假设我们有下面两个构造方法,他们返回值的类型是一样的。

func NewReadOnlyConnection(...) (*sql.DB, error)
func NewReadWriteConnection(...) (*sql.DB, error)

你可以通过Provide方法的dig.Name选项来给这两个构造方法的返回值命名。

c.Provide(NewReadOnlyConnection, dig.Name("ro"))
c.Provide(NewReadWriteConnection, dig.Name("rw"))

或者在内嵌了Dig.Out的结构体字段后面加上name:".."tag

type ConnectionResult struct {
  dig.Out

  ReadWrite *sql.DB `name:"rw"`
  ReadOnly  *sql.DB `name:"ro"`
}

func ConnectToDatabase(...) (ConnectionResult, error) {
  // ...
  return ConnectionResult{ReadWrite: rw, ReadOnly:  ro}, nil
}

不管是用上面的哪种方式,使用命名值时都需要用一个内嵌了dig.In的结构体中需要注入命名值的字段后面加上name:".."tag,tag的值要跟上面一致来接收注入的引用。

type GatewayParams struct {
  dig.In

  WriteToConn  *sql.DB `name:"rw"`
  ReadFromConn *sql.DB `name:"ro"`
}

这个tag可以跟可选参数的tag组合使用。

type GatewayParams struct {
  dig.In

  WriteToConn  *sql.DB `name:"rw"`
  ReadFromConn *sql.DB `name:"ro" optional:"true"`
}

func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
  if p.ReadFromConn == nil {
    log.Print("Warning: Using RW connection for reads")
    p.ReadFromConn = p.WriteToConn
  }
  // ...
}

3.8 Value Groups(值组)

dig提供了组值功能,用于生产合消费多个相同类型的对象。组值允许构造方法返回一个命名的无序的集合到dig容器中。其他的构造方法可以用切片来请求这个集合的所有值。

在内嵌了dig.Out的结构体中,在需要使用组值功能的字段后面加上group:".."tag。

type HandlerResult struct {
  dig.Out

  Handler Handler `group:"server"`
}

func NewHelloHandler() HandlerResult {
  ..
}

func NewEchoHandler() HandlerResult {
  ..
}

任意个构造函数都可以向这个组中加入值,其他构造函数可以通过使用一个内嵌了dig.In的结构体中一个加了group:".."tag的切片字段来接收这个组的值。这些构造方法并没有特定的执行顺序。

type ServerParams struct {
  dig.In

  Handlers []Handler `group:"server"`
}

func NewServer(p ServerParams) *Server {
  server := newServer()
  for _, h := range p.Handlers {
    server.Register(h)
  }
  return server
}

注意:这个组值中的值是无序的

可以在内嵌了dig.Out 的结构体中使用多个切片来为组提供值,但是考虑到组是以切片的形式来注入值的,这意味着在后面在内嵌了 dig.In的结构体中是以切片的切片形式注入的。从 dig v1.9.0 开始,如果您想为组提供单个元素而不是切片本身,可以在内嵌了dig.Out 的结构体中的向组添加值的切片后面添加 flatten 修饰符。(这个写的有点绕,意思是你如果是用切片来向容器中添加值的,到时dig给你注入的就是切片的切片(二维数组),可以通过加上flatten 修饰符,dig会将这个向容器中添加的切片展开为单个元素,然后后续使用这个组值时给你注入的就是切片了(一维数组))

type IntResult struct {
  dig.Out

  Handler []int `group:"server"`         // [][]int from dig.In
  Handler []int `group:"server,flatten"` // []int from dig.In
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容