zoukankan      html  css  js  c++  java
  • 分解uber依赖注入库dig-使用篇

    golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个

    • 谷歌出的wire,这个是用抽象语法树在编译时实现的。
    • uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依赖框架fx

    本系列分几部分,先对dig进行分析,第一篇介绍dig的使用,第二篇再从源码来剖析他是如何通过返射实现的的依赖注入的,后续会介绍fx 的使用和实现原理。
    dig主要的思路是能过Provider将不同的函数注册到容器内,一个函数可以通过参数来声明对其他函数返回值的依赖。在Invoke的时候才会真正的去调用容器内相应的Provider方法。
    dig还提供了可视化的方法Visualize用于生成dot有向图代码,更直观的观察依赖关系,关于dot的基本语法,可以查看帖子dot 语法总结
    帖子中的代码使用的digmaster分支,版本为1.11.0-dev,帖子所有的代码都在github上,地址:fx_dig_adventure

    简单使用

    func TestSimple1(t *testing.T) {
    	type Config struct {
    		Prefix string
    	}
    
    	c := dig.New()
    
    	err := c.Provide(func() (*Config, error) {
    		return &Config{Prefix: "[foo] "}, nil
    	})
    	if err != nil {
    		panic(err)
    	}
    	err = c.Provide(func(cfg *Config) *log.Logger {
    		return log.New(os.Stdout, cfg.Prefix, 0)
    	})
    	if err != nil {
    		panic(err)
    	}
    	err = c.Invoke(func(l *log.Logger) {
    		l.Print("You've been invoked")
    	})
    	if err != nil {
    		panic(err)
    	}
    }
    

    输出

    [foo] You've been invoked
    

    可以生成dot图,来更直观的查看依赖关系

    	b := &bytes.Buffer{}
    	if err := dig.Visualize(c, b); err != nil {
    		panic(err)
    	}
    	fmt.Println(b.String())
    

    输出

    digraph {
            rankdir=RL;
            graph [compound=true];
                    subgraph cluster_0 {
                            label = "main";
                            constructor_0 [shape=plaintext label="main.func1"];
                            
                            "*main.Config" [label=<*main.Config>];
                            
                    }
                    subgraph cluster_1 {
                            label = "main";
                            constructor_1 [shape=plaintext label="main.func2"];
                            
                            "*log.Logger" [label=<*log.Logger>];
                            
                    }
                    constructor_1 -> "*main.Config" [ltail=cluster_1];
    }
    
    

    可以看到 func2返回的参数为Log 依赖 func1返回参数 Configdot 语法总结
    展示出来:

    命名参数--多个返回相同类型的Provide

    如果Provide里提供的函数,有多个函数返回的数据类型是一样的怎么处理?比如,我们的数据库有主从两个连接库,怎么进行区分?
    dig可以将Provide命名以进行区分
    我们可以直接在Provide函数里使用dig.Name,为相同的返回类型设置不同的名字来进行区分。

    func TestName1(t *testing.T) {
    	type DSN struct {
    		Addr string
    	}
    	c := dig.New()
    
    	p1 := func() (*DSN, error) {
    		return &DSN{Addr: "primary DSN"}, nil
    	}
    	if err := c.Provide(p1, dig.Name("primary")); err != nil {
    		t.Fatal(err)
    	}
    
    	p2 := func() (*DSN, error) {
    		return &DSN{Addr: "secondary DSN"}, nil
    	}
    	if err := c.Provide(p2, dig.Name("secondary")); err != nil {
    		t.Fatal(err)
    	}
    
    	type DBInfo struct {
    		dig.In
    		PrimaryDSN   *DSN `name:"primary"`
    		SecondaryDSN *DSN `name:"secondary"`
    	}
    
    	if err := c.Invoke(func(db DBInfo) {
    		t.Log(db.PrimaryDSN)
    		t.Log(db.SecondaryDSN)
    	}); err != nil {
    		t.Fatal(err)
    	}
    }
    

    输出

    &{primary DSN}
    &{secondary DSN}
    

    dot

    这样做并不通用,一般我们是有一个结构体来实现,dig也有相应的支持,用一个结构体嵌入dig.out来实现,
    相同类型的字段在tag里设置不同的name来实现

    func TestName2(t *testing.T) {
    	type DSN struct {
    		Addr string
    	}
    	c := dig.New()
    
    	type DSNRev struct {
    		dig.Out
    		PrimaryDSN   *DSN `name:"primary"`
    		SecondaryDSN *DSN `name:"secondary"`
    	}
    	p1 := func() (DSNRev, error) {
    		return DSNRev{PrimaryDSN: &DSN{Addr: "Primary DSN"},
    			SecondaryDSN: &DSN{Addr: "Secondary DSN"}}, nil
    	}
    
    	if err := c.Provide(p1); err != nil {
    		t.Fatal(err)
    	}
    
    	type DBInfo struct {
    		dig.In
    		PrimaryDSN   *DSN `name:"primary"`
    		SecondaryDSN *DSN `name:"secondary"`
    	}
    	inv1 := func(db DBInfo) {
    		t.Log(db.PrimaryDSN)
    		t.Log(db.SecondaryDSN)
    	}
    
    	if err := c.Invoke(inv1); err != nil {
    		t.Fatal(err)
    	}
    }
    
    

    输出

    &{primary DSN}
    &{secondary DSN}
    

    dot

    和上面的不同之处就是一个function返回了两个相同类型的字段。

    组--把同类型的参数放在一个slice里

    如果有很多相同类型的返回参数,可以把他们放在同一个slice里,和命名方式一样,有两种使用方式
    第一种在调用Provide时直接使用dig.Group

    func TestGroup1(t *testing.T) {
    	type Student struct {
    		Name string
    		Age  int
    	}
    	NewUser := func(name string, age int) func() *Student {
    		return func() *Student {
    			return &Student{name, age}
    		}
    	}
    	container := dig.New()
    	if err := container.Provide(NewUser("tom", 3), dig.Group("stu")); err != nil {
    		t.Fatal(err)
    	}
    	if err := container.Provide(NewUser("jerry", 1), dig.Group("stu")); err != nil {
    		t.Fatal(err)
    	}
    	type inParams struct {
    		dig.In
    
    		StudentList []*Student `group:"stu"`
    	}
    	Info := func(params inParams) error {
    		for _, u := range params.StudentList {
    			t.Log(u.Name, u.Age)
    		}
    		return nil
    	}
    	if err := container.Invoke(Info); err != nil {
    		t.Fatal(err)
    	}
    }
    

    输出

    jerry 1
    tom 3
    

    生成dot

    或者使用结构体嵌入dig.Out来实现,tag里要加上了group标签

    	type Rep struct {
    		dig.Out
    		StudentList []*Student `group:"stu,flatten"`
    	}
    

    这个flatten的意思是,底层把组表示成[]*Student,如果不加flatten会表示成[][]*Student
    完整示例

    func TestGroup2(t *testing.T) {
    	type Student struct {
    		Name string
    		Age  int
    	}
    	type Rep struct {
    		dig.Out
    		StudentList []*Student `group:"stu,flatten"`
    	}
    	NewUser := func(name string, age int) func() Rep {
    		return func() Rep {
    			r := Rep{}
    			r.StudentList = append(r.StudentList, &Student{
    				Name: name,
    				Age:  age,
    			})
    			return r
    		}
    	}
    
    	container := dig.New()
    	if err := container.Provide(NewUser("tom", 3)); err != nil {
    		t.Fatal(err)
    	}
    	if err := container.Provide(NewUser("jerry", 1)); err != nil {
    		t.Fatal(err)
    	}
    	type InParams struct {
    		dig.In
    
    		StudentList []*Student `group:"stu"`
    	}
    	Info := func(params InParams) error {
    		for _, u := range params.StudentList {
    			t.Log(u.Name, u.Age)
    		}
    		return nil
    	}
    	if err := container.Invoke(Info); err != nil {
    		t.Fatal(err)
    	}
    }
    

    输出

    jerry 1
    tom 3
    

    生成dot

    dot图可以看出有两个方法生成了Group: stu

    需要注意的一点是,命名方式和组方式不能同时使用。

    可选参数

    如果注册的方法返回的参数是可以为nil的,可以使用option来实现

    func TestOption1(t *testing.T) {
    	type Student struct {
    		dig.Out
    		Name string
    		Age  *int `option:"true"`
    	}
    
    	c := dig.New()
    	if err := c.Provide(func() Student {
    		return Student{
    			Name: "Tom",
    		}
    	}); err != nil {
    		t.Fatal(err)
    	}
    
    	if err := c.Invoke(func(n string, age *int) {
    		t.Logf("name: %s", n)
    		if age == nil {
    			t.Log("age is nil")
    		} else {
    			t.Logf("age: %d", age)
    		}
    	}); err != nil {
    		t.Fatal(err)
    	}
    }
    

    输出

    name: Tom
    age is nil
    

    dry run

    如果我们只是想看一下依赖注入的整个流程是不是通的,可以通过dry run来跑一下,他不会调用具体的函数,而是直接返回函数的返回参数的zero

    func TestDryRun1(t *testing.T) {
    	// Dry Run
    	c := dig.New(dig.DryRun(true))
    
    	type Config struct {
    		Prefix string
    	}
    	err := c.Provide(func() (*Config, error) {
    		return &Config{Prefix: "[foo] "}, nil
    	})
    	if err != nil {
    		panic(err)
    	}
    	err = c.Provide(func(cfg *Config) *log.Logger {
    		return log.New(os.Stdout, cfg.Prefix, 0)
    	})
    	if err != nil {
    		panic(err)
    	}
    	err = c.Invoke(func(l *log.Logger) {
    		l.Print("You've been invoked")
    	})
    	if err != nil {
    		panic(err)
    	}
    }
    

    运行代码不会有任何输出。

    作者:李鹏
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    jchdl
    jchdl
    UVa 10256 (判断两个凸包相离) The Great Divide
    UVa 11168 (凸包+点到直线距离) Airport
    LA 2572 (求可见圆盘的数量) Kanazawa
    UVa 10652 (简单凸包) Board Wrapping
    UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
    UVa 10674 (求两圆公切线) Tangents
    UVa 11796 Dog Distance
    LA 3263 (平面图的欧拉定理) That Nice Euler Circuit
  • 原文地址:https://www.cnblogs.com/li-peng/p/14708132.html
Copyright © 2011-2022 走看看