精华内容
下载资源
问答
  • GO 依赖注入
    2021-04-06 19:29:52

    依赖注入是软件工程中经常使用到的一种技术,它提供了一种控制反转的机制,把控制权利交给了调用方。调用方来决定使用哪些参数,哪些对象来进行具体的业务逻辑。

    依赖注入的本质其实是为了将组件的创建与其依赖的创建分离

    实现原理:

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

    好处:
    1 它让调用方更灵活。
    2 大量减少定义类型的代码量
    3 增加代码的可用性,因为调用方只需要关注它需要的参数,不需要顾及它不需要的参数了。

    正常我们创建一个对象,首先需要构建对象的构造函数,调用时将入参加入进去,然后其中一旦有一个入参需要更改,所有函数的入参都需要修改:

    type Cat struct {
        color String
    }
    
    func (cat *Cat) Start() {
        pass
    }
    
    func NewCat() *Cat {
        return &Cat{
            color :      "read"
        }
    }
    
    
    
    type Person struct {
        cat *Cat
    }
    
    func (service *Person) Start() []*Person {
        service.cat.Start()
    }
    
    func NewPerson(cat *Cat) *Person{
        return &Person{cat: cat}
    }
    
    func main() {
        cat := NewCat()
        person := NewPerson(cat)
    
        person.Satrt()
    }

    而如果使用以来注入的方式:

    type Cat struct {
        color String
    }
    
    func (cat *Cat) Start() {
        pass
    }
    
    func NewCat() *Cat {
        return &Cat{
            color :      "read"
        }
    }
    
    
    
    type Person struct {
        cat *Cat
    }
    
    func (service *Person) Start() []*Person {
        service.cat.Start()
    }
    
    func NewPerson(cat *Cat) *Person{
        return &Person{cat: cat}
    }
    func BuildContainer() *dig.Container {
        container := dig.New()
        container.Provide(NewCat)
        container.Provide(NewPerson)
        return container
    }
    func main() {
        container := BuildContainer()
    
        err := container.Invoke(func(person *Person) {
            person.Start()
        })
    
        if err != nil {
            panic(err)
        }
    }

    这个虽然看起来没有简化多少,但是如果依赖的比较多的时候,就需要初始化一堆对象,然后调用对应的函数,而依赖注入:

    • 容器认识到我们要求的是构建 persion
    • 它确定函数 NewPersion提供该类型
    • 接下来它确定 NewPersion函数依赖 Cat
    • 它找到了 Cat 的提供者,也就是 NewCat
    • NewCat 没有任何依赖关系,所以它被调用
    • NewCat 的结果是一个 Cat传递给 NewPersion
    • NewPersion的结果是 *persion 被传递给 Invoke

    这个就是依赖注入的本质,

     

     

     

    更多相关内容
  • golang 依赖注入

    2021-09-18 10:25:59
    我们在微服务框架kratos ...wire是由 google 开源的一个供 Go 语言使用的依赖注入代码生成工具。它能够根据你的代码,生成相应的依赖注入 go 代码。 而与其它依靠反射实现的依赖注入工具不同的是,wire 能在编译期(准

    What#

    wire是由 google 开源的一个供 Go 语言使用的依赖注入代码生成工具。它能够根据你的代码,生成相应的依赖注入 go 代码。

    而与其它依靠反射实现的依赖注入工具不同的是,wire 能在编译期(准确地说是代码生成时)如果依赖注入有问题,在代码生成时即可报出来,不会拖到运行时才报,更便于 debug。

    Why#

    理解依赖注入#

    什么是依赖注入?为什么要依赖注入? 依赖注入就是 Java 遗毒(不是)

    依赖注入 (Dependency Injection,缩写为 DI),可以理解为一种代码的构造模式(就是写法),按照这样的方式来写,能够让你的代码更加容易维护。

    对于很多软件设计模式和架构的理念,我们都无法理解他们要绕好大一圈做复杂的体操、用奇怪的方式进行实现的意义。他们通常都只是丢出来一段样例,说这样写就很好很优雅,由于省略掉了这种模式是如何发展出来的推导过程,我们只看到了结果,导致理解起来很困难。那么接下来我们来尝试推导还原一下整个过程,看看代码是如何和为什么演进到依赖注入模式的,以便能够更好理解使用依赖注入的意义。

    依赖是什么?#

    这里的依赖是个名词,不是指软件包的依赖(比如那坨塞在 node_modules 里面的东西),而是指软件中某一个模块(对象/实例)所依赖的其它外部模块(对象/实例)。

    注入到哪里?#

    被依赖的模块,在创建模块时,被注入到(即当作参数传入)模块的里面。

    不 DI 是啥样?DI 了又样子?#

    下面用 go 伪代码来做例子,领会精神即可。

    假设个场景,你在打工搞一个 web 应用,它有一个简单接口。最开始的项目代码可能长这个样子:

    # 下面为伪代码,忽略了很多与主题无关的细节
    type App struct {}
    # 假设这个方法将会匹配并处理 GET /biu/<id> 这样的请求func (a *App) GetData(id string) string {    # todo: write your data query    return "some data"}
    func NewApp() *App {    return &App{}}
    app := App()app.Run()

    Copy

    你要做的是接一个 mysql,从里面把数据按照 id 查出来,返回。 要连 mysql 的话,假设我们已经有了个NewMySQLClient的方法返回 client 给你,初始化时传个地址进去就能拿到数据库连接,并假设它有个Exec的方法给你执行参数。

    不用 DI,通过全局变量传递依赖实例#

    一种写法是,在外面全局初始化好 client,然后 App 直接拿来调用。

    
    var mysqlUrl = "mysql://blabla"var db = NewMySQLClient(mysqlUrl)
    
    type App struct {
    }
    func (a *App) GetData(id string) string {    data := db.Exec("select data from biu where id = ? limit 1", id)    return data}
    
    func NewApp() *App {    return &App{}}func main() {    app := App()    app.Run()}

    Copy

    这就是没用依赖注入,app 依赖了全局变量 db,这是比较糟糕的一种做法。db 这个对象游离在全局作用域,暴露给包下的其他模块,比较危险。(设想如果这个包里其他代码在运行时悄悄把你的这个 db 变量替换掉会发生啥)

    不用 DI,在 App 的初始化方法里创建依赖实例#

    另一种方式是这样的:

    type App struct {    db *MySQLClient}
    func (a *App) GetData(id string) string {    data := a.db.Exec("select data from biu where id = ? limit 1", id)    return data}
    
    func NewApp() *App {    return &App{db: NewMySQLClient(mysqlUrl)}}func main() {    app := NewApp("mysql://blabla")    app.Run()}

    Copy

    这种方法稍微好一些,db 被塞到 app 里面了,不会有 app 之外的无关代码碰它,比较安全,但这依然不是依赖注入,而是在内部创建了依赖,接下来你会看到它带来的问题。

    老板:我们的数据要换个地方存 (需要变更实现)#

    你的老板不知道从哪听说——Redis 贼特么快,要不我们的数据改从 Redis 里读吧。这个时候你的内心有点崩溃,但毕竟要恰饭的,就硬着头皮改上面的代码。

    type App struct {    ds *RedisClient}
    func (a *App) GetData(id string) string {    data := a.ds.Do("GET", "biu_"+id)    return data}
    
    func NewApp() *App {    return &App{ds: NewRedisClient(redisAddr)}}
    func main() {    app := NewApp("redis://ooo")    app.Run()}

    Copy

    上面基本进行了 3 处修改:

    1. App 初始化方法里改成了初始化 RedisClient
    2. get_data 里取数据时改用 run 方法,并且查询语句也换了
    3. App 实例化时传入的参数改成了 redis 地址

    老板:要不,我们再换个地方存?/我们要加测试,需要 Mock#

    老板的思路总是很广的,又过了两天他又想换成 Postgres 存了;或者让你们给 App 写点测试代码,只测接口里面的逻辑,通常我们不太愿意在旁边再起一个数据库,那么就需要 mock 掉数据源这块东西,让它直接返回数据给请求的 handler 用,来进行针对性的测试。

    这种情况怎么办?再改里面的代码?这不科学。

    面向接口编程#

    一个很重要的思路就是要面向接口(interface)编程,而不是面向具体实现编程。

    什么叫面向具体实现编程呢?比如上述的例子里改动的部分:调 mysqlclient 的 exec_sql 执行一条 sql,被改成了:调 redisclient 的 do 执行一句 get 指令。由于每种 client 的接口设计不同,每换一个实现,就得改一遍。

    而面向接口编程的思路,则完全不同。我们不要听老板想用啥就马上写代码。首先就得预料到,这个数据源的实现很有可能被更换,因此在一开始就应该做好准备(设计)。

    设计接口#

    Python 里面有个概念叫鸭子类型(duck-typing),就是如果你叫起来像鸭子,走路像鸭子,游泳像鸭子,那么你就是一只鸭子。这里的叫、走路、游泳就是我们约定的鸭子接口,而你如果完整实现了这些接口,我们可以像对待一个鸭子一样对待你。

    在我们上面的例子中,不论是 Mysql 实现还是 Redis 实现,他们都有个共同的功能:用一个 id,查一个数据出来,那么这就是共同的接口。

    我们可以约定一个叫 DataSource 的接口,它必须有一个方法叫 GetById,功能是要接收一个 id,返回一个字符串

    type DataSource interface {    GetById(id string) string}

    Copy

    然后我们就可以把各个数据源分别进行封装,按照这个 interface 定义实现接口,这样我们的 App 里处理请求的部分就可以稳定地调用 GetById 这个方法,而底层数据实现只要实现了 DataSource 这个 interface 就能花式替换,不用改 App 内部的代码了。

    // 封装个redistype redis struct {    r *RedisClient}
    func NewRedis(addr string) *redis {    return &redis{r: NewRedisClient(addr)}}
    func (r *redis) GetById(id string) string {    return r.r.Do("GET", "biu_"+id)}
    
    // 再封装个mysqltype mysql struct {    m *MySQLClient}
    func NewMySQL(addr string) *redis {    return &mysql{m: NewMySQLClient(addr)}}
    func (m *mysql) GetById(id string) string {    return r.m.Exec("select data from biu where id = ? limit 1", id)}
    
    type App struct {    ds DataSource}
    func NewApp(addr string) *App {    //需要用Mysql的时候    return &App{ds: NewMySQLClient(addr)}
        //需要用Redis的时候    return &App{ds: NewRedisClient(addr)}}
    

    Copy

    由于两种数据源都实现了 DataSource 接口,因此可以直接创建一个塞到 App 里面了,想用哪个用哪个,看着还不错?

    等一等,好像少了些什么#

    addr 作为参数,是不是有点简单?通常初始化一个数据库连接,可能有一堆参数,配在一个 yaml 文件里,需要解析到一个 struct 里面,然后再传给对应的 New 方法。

    配置文件可能是这样的:

    redis:    addr: 127.0.0.1:6379    read_timeout: 0.2s    write_timeout: 0.2s

    Copy

    解析结构体是这样的:

    type RedisConfig struct {    Network      string             `json:"network,omitempty"`    Addr         string             `json:"addr,omitempty"`    ReadTimeout  *duration.Duration `json:"read_timeout,omitempty"`    WriteTimeout *duration.Duration `json:"write_timeout,omitempty"`}

    Copy

    结果你的NewApp方法可能就变成了这个德性:

    func NewApp() *App {    var conf *RedisConfig    yamlFile, err := ioutil.ReadFile("redis_conf.yaml")    if err != nil {        panic(err)    }    err = yaml.Unmarshal(yamlFile, &conf)    if err != nil {        panic(err)    }    return &App{ds: NewRedisClient(conf)}}

    Copy

    NewApp 说,停停,你们年轻人不讲武德,我的责任就是创建一个 App 实例,我只需要一个 DataSource 注册进去,至于这个 DataSource 是怎么来的我不想管,这么一坨处理 conf 的代码凭什么要放在我这里,我也不想关心你这配置文件是通过网络请求拿来的还是从本地磁盘读的,我只想把 App 组装好扔出去直接下班。

    依赖注入终于可以登场了#

    还记得前面是怎么说依赖注入的吗?被依赖的模块,在创建模块时,被注入到(即当作参数传入)初始化函数里面。通过这种模式,正好可以让 NewApp 早点下班。我们在外面初始化好 NewRedis 或者 NewMysql,得到的 DataSource 直接扔给 NewApp。

    也就是这样

    func NewApp(ds DataSource) *App {    return &App{ds: ds}}

    Copy

    那坨读配置文件初始化 redis 的代码扔到初始化 DataSource 的方法里去

    func NewRedis() DataSource {    var conf *RedisConfig    yamlFile, err := ioutil.ReadFile("redis_conf.yaml")    if err != nil {        panic(err)    }    err = yaml.Unmarshal(yamlFile, &conf)    if err != nil {        panic(err)    }    return &redis{r: NewRedisClient(conf)}}

    Copy

    更进一步,NewRedis 这个方法甚至也不需要关心文件是怎么读的,它的责任只是通过 conf 初始化一个 DataSource 出来,因此你可以继续把读 config 的代码往外抽,把 NewRedis 做成接收一个 conf,输出一个 DataSource

    func GetRedisConf() *RedisConfigfunc NewRedis(conf *RedisConfig) DataSource

    Copy

    因为之前整个组装过程是散放在 main 函数下面的,我们把它抽出来搞成一个独立的 initApp 方法。最后你的 App 初始化逻辑就变成了这样

    func initApp() *App {    c := GetRedisConf()    r := NewRedis(c)    app := NewApp(r)    return app}
    func main() {    app := initApp()    app.Run()}

    Copy

    然后你可以通过实现 DataSource 的接口,更换前面的读取配置文件的方法,和更换创建 DataSource 的方法,来任意修改你的底层实现(读配置文件的实现,和用哪种 DataSource 来查数据),而不用每次都改一大堆代码。这使得你的代码层次划分得更加清楚,更容易维护了。

    这就是依赖注入。

    手工依赖注入的问题#

    上文这一坨代码,把各个实例初始化好,再按照各个初始化方法的需求塞进去,最终构造出 app 的这坨代码,就是注入依赖的过程。

    c := GetRedisConf()r := NewRedis(c)app := NewApp(r)

    Copy

    目前只有一个 DataSource,这样手写注入过程还可以,一旦你要维护的东西多了,比如你的 NewApp 是这样的NewApp(r *Redis, es *ES, us *UserSerivce, db *MySQL) *App然后其中 UserService 是这样的UserService(pg *Postgres, mm *Memcached),这样形成了多层次的一堆依赖需要注入,徒手去写非常麻烦。

    而这部分,就是 wire 这样的依赖注入工具能够起作用的地方了——他的功能只是通过生成代码帮你注入依赖,而实际的依赖实例需要你自己创建(初始化)。

    How#

    wire 的主要问题是,看文档学不会。反正我最初看完文档之后是一头雾水——这是啥,这要干啥?但通过我们刚才的推导过程,应该大概理解了为什么要用依赖注入,以及 wire 在这其中起到什么作用——通过生成代码帮你注入依赖,而实际的依赖实例需要你自己创建(初始化)。

    接下来就比较清楚了。

    首先要实现一个wire.go的文件,里面定义好 Injector。

    // +build wireinject
    func initApp() (*App) {    panic(wire.Build(GetRedisConf, NewRedis, SomeProviderSet, NewApp))}

    Copy

    然后分别实现好 Provider。

    执行wire命令后 他会扫描整个项目,并帮你生成一个wire_gen.go文件,如果你有什么没有实现好,它会报错出来。

    你学会了吗?

    重新理解#

    等一等,先别放弃治疗,让我们用神奇的中文编程来解释一下要怎么做。

    谁参与编译?#

    上面那个initApp方法,官方文档叫它 Injector,由于文件里首行// +build wireinject这句注释,这个 wire.go 文件只会由 wire 读取,在 go 编译器在编译代码时不会去管它,实际会读的是生成的 wire_gen.go 文件。

    而 Provider 就是你代码的一部分,肯定会参与到编译过程。

    Injector 是什么鬼东西?#

    Injector 就是你最终想要的结果——最终的 App 对象的初始化函数,也就是前面那个例子里的initApp方法。

    把它理解为你去吃金拱门,进门看到点餐机,噼里啪啦点了一堆,最后打出一张单子。

    // +build wireinject
    func 来一袋垃圾食品() 一袋垃圾食品 {    panic(wire.Build(来一份巨无霸套餐, 来一份双层鳕鱼堡套餐, 来一盒麦乐鸡, 垃圾食品打包))}

    Copy

    这就是你点的单子,它不参与编译,实际参与编译的代码是由 wire 帮你生成的。

    Provider 是什么鬼东西?#

    Provider 就是创建各个依赖的方法,比如前面例子里的 NewRedis 和 NewApp 等。

    你可以理解为,这些是金拱门的服务员和后厨要干的事情: 金拱门后厨需要提供这些食品的制作服务——实现这些实例初始化方法。

    func 来一盒麦乐鸡() 一盒麦乐鸡 {}func 垃圾食品打包(一份巨无霸套餐, 一份双层鳕鱼堡套餐, 一盒麦乐鸡) 一袋垃圾食品 {}

    Copy

    wire 里面还有个 ProviderSet 的概念,就是把一组 Provider 打包,因为通常你点单的时候很懒,不想这样点你的巨无霸套餐:我要一杯可乐,一包薯条,一个巨无霸汉堡;你想直接戳一下就好了,来一份巨无霸套餐。这个套餐就是 ProviderSet,一组约定好的配方,不然你的点单列表(injector 里的 Build)就会变得超级长,这样你很麻烦,服务员看着也很累。

    用其中一个套餐举例

    // 先定义套餐内容var 巨无霸套餐 = wire.NewSet(来一杯可乐,来一包薯条,来一个巨无霸汉堡)
    // 然后实现各个食品的做法func 来一杯可乐() 一杯可乐 {}func 来一包薯条() 一包薯条 {}func 来一个巨无霸汉堡() 一个巨无霸汉堡 {}

    Copy

    wire 工具做了啥?#

    重要的事情说三遍,通过生成代码帮你注入依赖

    在金拱门的例子里就是,wire 就是个服务员,它按照你的订单,去叫做相应的同事把各个食物/套餐做好,然后最终按需求打包给你。这个中间协调构建的过程,就是注入依赖。

    这样的好处就是, 对于金拱门,假设他们突然换可乐供应商了,直接把来一杯可乐替换掉就行,返回一种新的可乐,而对于顾客不需要有啥改动。 对于顾客来说,点单内容可以变换,比如我今天不想要麦乐鸡了,或者想加点别的,只要改动我的点单(只要金拱门能做得出来),然后通过 wire 重新去生成即可,不需要关注这个服务员是如何去做这个订单的。

    现在你应该大概理解 wire 的用处和好处了。

    总结#

    让我们从金拱门回来,重新总结一下用 wire 做依赖注入的过程。

    1. 定义 Injector#

    创建wire.go文件,定义下你最终想用的实例初始化函数例如initApp(即 Injector),定好它返回的东西*App,在方法里用panic(wire.Build(NewRedis, SomeProviderSet, NewApp))罗列出它依赖哪些实例的初始化方法(即 Provider)/或者哪些组初始化方法(ProviderSet)

    2. 定义 ProviderSet(如果有的话)#

    ProviderSet 就是一组初始化函数,是为了少写一些代码,能够更清晰的组织各个模块的依赖才出现的。也可以不用,但 Injector 里面的东西就需要写一堆。 像这样 var SomeProviderSet = wire.NewSet(NewES,NewDB)定义 ProviderSet 里面包含哪些 Provider

    3. 实现各个 Provider#

    Provider 就是初始化方法,你需要自己实现,比如 NewApp,NewRedis,NewMySQL,GetConfig 等,注意他们们各自的输入输出

    4. 生成代码#

    执行 wire 命令生成代码,工具会扫描你的代码,依照你的 Injector 定义来组织各个 Provider 的执行顺序,并自动按照 Provider 们的类型需求来按照顺序执行和安排参数传递,如果有哪些 Provider 的要求没有满足,会在终端报出来,持续修复执行 wire,直到成功生成wire_gen.go文件。接下来就可以正常使用initApp来写你后续的代码了。

    如果需要替换实现,对 Injector 进行相应的修改,实现必须的 Provider,重新生成即可。

    它生成的代码其实就是类似我们之前需要手写的这个

    func initApp() *App {  // injector    c := GetRedisConf() // provider    r := NewRedis(c)  // provider    app := NewApp(r) // provider    return app}

    Copy

    由于我们的例子比较简单,通过 wire 生成体现不出优势,但如果我们的软件复杂,有很多层级的依赖,使用 wire 自动生成注入逻辑,无疑更加方便和准确。

    5. 高级用法#

    wire 还有更多功能,比如 cleanup, bind 等等,请参考官方文档来使用。

    最后,其实多折腾几次,就会使用了,希望本文能对您起到一定程度上的帮助。

    展开全文
  • golang依赖注入——wire

    2021-01-07 21:36:54
    最近在做golang的框架,发现golang同样需要类似java中spring一样的ioc依赖注入框架。 如果项目规模小的情况下,是否有依赖注入框架问题不大,但是当项目变大之后,有一个合适的依赖注入框架是十分必要的。 通过调研...
  • Go依赖注入-wire 依赖注入是什么? 第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前没学过Java和spring,所以第一次接触这个词的时候...
    • 什么是依赖注入
    • 依赖注入的好处
    • Go的依赖注入-wire

    依赖注入是什么?

    第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前没学过Java和spring,所以第一次接触这个词的时候是很懵的。

    依赖注入,英文名dependency injection,简称DI。依赖两个字很好理解,在软件设计上,从架构模块到函数方法都存在大大小小的依赖关系。

    比如说在new A 之前需要先new B ,A依赖于B,这时候我们就可以说B是A的依赖,A控制B,AB之间存在着耦合的关系,而代码设计思想是最好可以做到松耦合。如果某一天B需要改造那么A也需要跟着改造。这是一个依赖你可以觉得没问题,但如果是A->B->C->D->E->F之间存在一连串的依赖关系,那么改造起来就会十分麻烦。

    这个时候就需要一种东西来解开他们之间的强耦合,怎么解耦呢,只能借助第三方力量了,我们把A对B的控制权交给第三方,这种思想就称为控制反转(IOC Inversion Of Control),这个第三方称为IOC容器。而IOC容器要做的事情就是new一个B出来,然后把这个B的实例注入到A里面去,然后A就可以正常的使用基于B的方法了,这个过程被称为依赖项注入,而基于IOC的这种方法就叫做依赖注入。

    依赖注入的好处

    明白了依赖注入的思想,应该也就明白了其带来的最大好处——解耦。

    而解耦又能带来更多的好处:代码扩展性增强,代码的可维护性增强,更容易进行单元测试等等。

    那么依赖注入如何实现呢?

    Java中有以下几种方式:

    1. setter方法注入:实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
    2. 基于接口的注入:实现特定接口以供外部容器注入所依赖类型的对象。
    3. 基于构造函数的注入:实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
    4. 基于注解的注入:在代码里加上特定的关键字实现注入。

    注解是最常见的方式,它像注释一样不被当做代码来执行,而是专门供别人阅读。但注释的读者完全是人类,而注解的主要读者除了人类之外还有框架或预编译器。

    Go依赖注入-wire

    wire就是一种基于注解的依赖注入方式。wire是 Google 开源的一个依赖注入工具,我们只需要在一个特殊的go文件中告诉wire类型之间的依赖关系,它会自动帮我们生成代码,帮助我们创建指定类型的对象,并组装它的依赖。

    wire有两个基础概念,Provider(构造器)和Injector(注入器)。

    通过提供provider函数,让wire知道如何产生这些依赖对象。wire根据我们定义的injector函数签名,生成完整的injector函数,injector函数是最终我们需要的函数,它将按依赖顺序调用provider

    wire的要求很简单,新建一个wire.go文件(文件名可以随意),创建我们的初始化函数。比如,我们要创建并初始化一个Mission对象,我们就可以这样:

    //+build wireinject
    
    package main
    
    import "github.com/google/wire"
    
    func InitMission(name string) Mission {
      wire.Build(NewMonster, NewPlayer, NewMission)
      return Mission{}
    }
    

    可以看到第一行的注解:+build wireinject,表示这是一个注入器。+build其实是 Go 语言的一个特性。类似 C/C++ 的条件编译,在执行go build时可传入一些选项,根据这个选项决定某些文件是否编译。wire工具只会处理有wireinject的文件,所以我们的wire.go文件要加上这个。

    在函数中,我们调用wire.Build()将创建Mission所依赖的类型的构造器传进去。例如,需要调用NewMission()创建Mission类型,NewMission()接受两个参数一个Monster类型,一个Player类型。Monster类型对象需要调用NewMonster()创建,Player类型对象需要调用NewPlayer()创建。所以NewMonster()NewPlayer()我们也需要传给wire

    写完wire.go文件之后执行wire命令,就会自动生成一个wire_gen.go文件。

    // Code generated by Wire. DO NOT EDIT.
    
    //go:generate wire
    //+build !wireinject
    
    package main
    
    // Injectors from wire.go:
    
    func InitMission(name string) Mission {
      player := NewPlayer(name)
      monster := NewMonster()
      mission := NewMission(player, monster)
      return mission
    }
    

    可以看到wire自动帮我们生成了InitMission方法,此方法中依次初始化了player,monster和mission。之后在我们的main函数中就只需调用这个InitMission即可。

    func main() {
      mission := InitMission("dj")
    
      mission.Start()
    }
    

    而在没用依赖注入之前,我们的代码是这样的:

    func main() {
      monster := NewMonster()
      player := NewPlayer("dj")
      mission := NewMission(player, monster)
    
      mission.Start()
    }
    

    是不是简洁了很多。这里只有三个对象的初始化,如果是更多可能才会意识到依赖注入的好处。

    比如:

    wire.go文件:
    // +build wireinject
    // The build tag makes sure the stub is not built in the final build.
    
    package di
    
    import (
    	"github.com/google/wire"
    )
    
    //go:generate kratos t wire
    func InitApp() (*App, func(), error) {
    	panic(wire.Build(dao.Provider, service.Provider, http.New, grpc.New, NewApp))
    }
    
    实现文件:
    //dao
    var Provider = wire.NewSet(New, NewDB, NewRedis)
    //service
    var Provider = wire.NewSet(New, wire.Bind(new(pb.Server), new(*Service)))
    
    
    生成的wire_gen.go 文件:
    func InitApp() (*App, func(), error) {
    	redis, cleanup, err := dao.NewRedis()
    	if err != nil {
    		return nil, nil, err
    	}
    	db, cleanup2, err := dao.NewDB()
    	if err != nil {
    		cleanup()
    		return nil, nil, err
    	}
    	daoDao, cleanup3, err := dao.New(redis, db)
    	if err != nil {
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	serviceService, cleanup4, err := service.New(daoDao)
    	if err != nil {
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	engine, err := http.New(serviceService)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	server, err := grpc.New(serviceService)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	app, cleanup5, err := NewApp(serviceService, engine, server)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	return app, func() {
    		cleanup5()
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    	}, nil
    }
    
    

    所以,依赖注入到底是什么?

    封装解耦罢了。

    展开全文
  • 一个高效而强大的Go依赖注入容器– 目录 介绍 cargo是一个库,它通过使用Container提供了一种强大的方式来处理对象及其依赖项。 容器通过构造函数注入实现模式来工作,从而产生显式依赖并实现了控制反转原理。 安装...
  • dig一个用于Go语言基于反射的依赖注入工具包
  • go 版本依赖注入

       使用过 Java 的一定知道依赖注入这个概念,说到依赖注入就不得不提一下控制反转(IOC),这个两个不是什么复杂的技术,而是一种思想,一种设计模式,接下来我谈下我对这个设计模式的理解,以及如何使用golang实现依赖注入。

    一、控制反转是什么?

    控制反转即Ioc(后续使用 Ioc 来表述控制反转),Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

    1、如何理解 Ioc 呢?

       理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转?什么是正转?反转了是为什么什么?带着疑问往下看。

    1. 谁控制谁?
         在程序中,我们直接申明一个对象,是程序主动去创建依赖的对象;Ioc的思想是专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;Ioc容器控制了对象,控制对象的获取。

    2. 什么是正转?
         说反转之前,先了解下什么是正转,正转顾名思义,程序中由我们自己在对象中主动控制去直接获取依赖对象。

    3. 什么是反转?
         反转即,由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

    2、控制反转和依赖注入

    DI—Dependency Injection,即“依赖注入”(后续使用 DI 来表述依赖注入)

       组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
       通过依赖注入机制,我们只需要通过简单的初始化,而无需任何代码就可指定目标依赖对象,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

    1. 谁依赖谁?
         程序依赖 Ioc 容器
    2. 谁注入谁?
         Ioc 容器注入应用程序某个对象,应用程序依赖的对象
    3. 注入了什么?
         注入某个对象所需要的资源

    3、小总结

    Ioc 和 DI 是同一个概念的不同角度描述,设计理念是一样的。相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

    二、依赖注入代码实现

    欢迎点赞、使用、提出改进意见:点击跳转 github,下方讲解核心的实现流程。

    1. 初始化 core.go

      type LoadFunc func(ILoader) error
      var (
      	ErrNotFound = errors.New("key not found")
      	shared      = NewSingleLoader()
      	funcs       = make([]LoadFunc, 0, 16)
      )
      
      func DefaultLoader() ILoader {
      	return shared
      }
      type ILoader interface {
      	Register(key interface{}, value interface{}) error
      	// 加载所有注册变量
      	LoadingAll()
      	// 加载某结构体下变量
      	Loading(v interface{})
      }
      
      // 注册 注册方法
      func Register(f LoadFunc) {
      	funcs = append(funcs, f)
      }
      
      // 将 core 的 funcs 保存的注册方法
      // 全局注入到 loader 的集合中
      func LoadingAll(loader ILoader) (err error) {
      	for _, f := range funcs {
      		err = f(loader)
      		if err != nil {
      			return err
      		}
      	}
      	loader.LoadingAll()
      	return
      }
      
      
    2. 初始化 single.go

      var _ ILoader = new(singleLoader)
      
      // 初始化单例装载器
      func NewSingleLoader() ILoader {
      	return &singleLoader{
      		objs: make(map[interface{}]reflect.Value),
      	}
      }
      
      // 全局 Ioc容器
      type singleLoader struct {
      	objs map[interface{}]reflect.Value
      }
      
      func (s *singleLoader) Register(key interface{}, value interface{}) error {
      	_, ok := s.objs[key]
      	if ok {
      		return errors.New(fmt.Sprintf("key duplicate: %v", key))
      	}
      	s.objs[key] = reflect.ValueOf(value)
      	return nil
      }
      func (s *singleLoader) LoadingAll() {
      	for _, v := range s.objs {
      		s.Loading(v)
      	}
      }
      
      // 装载结构体中依赖的字段
      func (s *singleLoader) Loading(v interface{}) {
      	var value reflect.Value
      	var ok bool
      	if value, ok = v.(reflect.Value); !ok {
      		value = reflect.ValueOf(v)
      	}
      loop:
      	for {
      		switch value.Kind() {
      		case reflect.Ptr:
      			value = value.Elem()
      		case reflect.Interface:
      			value = value.Elem()
      		default:
      			break loop
      		}
      	}
      
      	if value.Kind() != reflect.Struct {
      		return
      	}
      	for i := 0; i < value.NumField(); i++ {
      		name := value.Type().Field(i).Tag.Get(LoaderTag)
      		temp, ok := s.objs[name]
      		if ok {
      			field := value.Field(i)
      			if field.CanSet() {
      				field.Set(temp)
      			} else {
      				field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem()
      				field.Set(temp)
      			}
      		}
      	}
      
    展开全文
  • Golang 实现依赖注入

    2021-04-29 01:21:40
    Golang 实现依赖注入 什么是依赖注入 和 使用 uber.org/dig 对 Go 项目进行依赖注入 使用 google/wire 对 Go 项目进行依赖注入
  • 这样带来的一个问题是,如果有模块要修改,势必会影响其他模块,如果模块很多,还需要人为的规定初始化顺序,这对大型项目的维护来说是一件很恐怖的事,因此依赖注入的引入可以减少维护依赖关系的精力,专心使开
  • 该存储库包含一个受Spring Boot Bean System启发的Golang依赖注入器 产品特点 自动连接所需的所有依赖项 结构喷射器 可变喷油器 如何添加到您的项目 go get -u github.com/hsjsjsj009/go-beans 如何在您的项目中...
  • go语言依赖注入实现

    2020-05-25 13:40:53
    最近做项目中,生成对象还是使用比较原始的New和简单工厂的方式,使用过程中感觉不太爽快(依赖紧密,有点改动就比较麻烦),还是比较喜欢使用依赖注入的方式。 然后网上没有找到比较好用的依赖注入包,就自己动手写了...
  • Golang 依赖注入

    万次阅读 2021-06-09 16:28:16
    1. 手动实现依赖注入 package main import "fmt" type A struct { B *B } type B struct { Did int } func main() { var a A b := B{Did: 1} fmt.Println(b.Did) a.B = &b fmt.Println(a.B.Did) } ...
  • goioc / di:依赖注入 为什么在DI中使用DI? 为什么要使用IoC? 我已经通过在Java中使用依赖注入近10年了。 我并不是说不能没有它,但是事实证明,它对于大型企业级应用程序非常有用。 您可能会争辩说Go遵循完全...
  • 本文介绍了golang不到30行代码实现依赖注入的方法,分享给大家,具体如下: 项目地址 go-di-demo 本项目依赖 使用标准库实现,无额外依赖 依赖注入的优势 用java的人对于spring框架一定不会陌生,spring核心就是...
  • go学习 --- 依赖注入

    2022-02-13 21:19:23
    一、依赖注入 package main import "fmt" func a1() { fmt.Println("a1调用") } func a2() { fmt.Println("a2调用") } func main() { //通过map实现依赖注入 myMap := make(map[string]func()) myMap["a1"]...
  • go依赖注入wire

    2022-04-26 09:42:09
    参考https://lailin.xyz/post/go-training-week4-wire.html 一 安装 go get github....项目启动初始化过程中 会依赖很多库包,自己写会导致库的循环引用 用wire 可以避免循环引用以及依赖缺失 二 简单使.
  • Go 依赖注入库dig

    2020-09-13 17:15:37
    今天我们来介绍 Go 语言的一个依赖注入(DI)库——dig。dig 是 uber 开源的库。Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring。相比庞大的 Spring,dig 很小巧,实现和使用都...
  • main函数的初始化优化-依赖注入
  • Go 语言依赖注入

    2019-05-09 19:58:53
    Go语言的接口设计,避免了很多需要使用第三方依赖注入框架的情况(比如Java,等等)。我们的注入方案只提供非常少的类似Dager或Guice中的注入方案,而专注于尽量避免手动去配置对象和组件之间的依赖关系。因为,我们...
  • 用于Golang的基于反射的依赖注入工具箱。 该自述文件处于进行中状态。 安装 整个项目基于go模块。 要获取最新版本,请使用go1.16 +并使用go get命令获取它。 例如: go get github.com/go-autowire/autowire ...
  • 轻松,快速且类型安全的Go依赖注入。 嘲笑时钟 模拟运行时依赖项 安装 go get -u github.com/elliotchance/dingo 建造容器 构建或重建容器的操作如下: dingo 该容器是从与运行dingo命令所在目录相同的目录dingo...
  • Wire:Go Wire中的自动初始化是一种代码生成工具,可使用依赖注入自动连接组件。 组件之间的依赖关系在Wire中表示为功能参数,这鼓励使用Wire:Go Wire中的自动初始化是一种代码生成工具,可使用依赖注入自动连接...
  • 代码生成工具,使用依赖注入自动连接组件
  • Golang依赖注入框架wire使用详解

    千次阅读 2020-09-24 16:23:02
    What is wire? wire是google开源的依赖注入框架。或者引用官方的话来说:“Wire is a code generation ...除了wire,Go依赖注入框架还有Uber的dig和Facebook的inject,它们都是使用反射机制来实现运行时依赖注入(r
  • golang实现依赖注入

    2019-12-30 21:05:16
    golang实现依赖注入 依赖注入是软件工程中经常使用到的一种技术,它提供了一种控制反转的机制,把控制权利交给了调用方。调用方来决定使用哪些参数,哪些对象来进行具体的业务逻辑。 它有几个好处: 1 它让调用方...
  • golang 依赖注入应用

    2020-03-25 11:39:40
    package main import ( "fmt" "github.com/facebookgo/inject" "os" ) type Client interface{ TestServer() } type Test struct { Name string Address string } type Two st...
  • 深入解析go依赖注入go.uber.org/fx

    千次阅读 2021-10-24 19:28:45
    初识依赖注入来自开源项目Grafana 的源码,该项目框架采用依赖注入方式对各结构体字段进行赋值。DI 依赖注入包为https://github.com/facebookarchive/inject,后面我会专门介绍这个包依赖注入的原理。不过今天的主角...
  • Go-injectGo的依赖注入

    2019-08-14 02:59:25
    inject包提供了多种对实体的映射和依赖注入方式。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,687
精华内容 6,274
关键字:

go依赖注入

友情链接: tongzhouqiucha.zip