本文主要是介绍Go 项目必备:深入浅出 Wire 依赖注入工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
当项目中实例依赖(组件)的数量越来越多,如果还是人工手动编写初始化代码和维护组件之间依赖关系的话,会是一件非常繁琐的事情,而且在大仓中尤其明显。因此,社区里已经有了不少的依赖注入框架。
除了来自 Google 的 Wire 以外,还有 Dig(Uber) 、Inject(Facebook)。其中 Dig 和 Inject 都是基于 Golang 的 Reflection 来实现的。这不仅对性能产生影响,而且依赖注入的机制对使用者不透明,非常的“黑盒”。
Clear is better than clever ,Reflection is never clear.
— Rob Pike
相比之下,Wire 完全基于代码生成。在开发阶段,wire 会自动生成组件的初始化代码,生成代码人类可读,可以提交仓库,也可以正常编译。因此 Wire 的依赖注入非常透明,也不会带来运行阶段的任何性能损耗。
Wire
Wire
是一个专为依赖注入(Dependency Injection
)设计的代码生成工具,它可以自动生成用于初始化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。
Wire 安装
我们可以执行以下命令来安装 Wire
工具:
go install github.com/google/wire/cmd/wire@latest
安装之前请确保已将 $GOPATH/bin
添加到环境变量 $PATH
里。
Wire 使用
前置代码准备
虽然我们在前面已经通过 go install
命令安装了 Wire
命令行工具,但在具体项目中,我们仍然需要通过以下命令安装项目所需的 Wire
依赖,以便结合 Wire
工具生成代码:
go get github.com/google/wire@latest
1.创建 wire.go 文件
在生成代码之前,我们先声明各个组件的依赖关系和初始化顺序。在应用入口创建一个 wire.go 文件。
// +build wireinjectpackage mainimport "..." // 简化示例var ProviderSet = wire.NewSet(configs.Get,databases.New,repositories.NewUser,services.NewUser,NewApp,
)func CreateApp() (*App, error) {wire.Build(ProviderSet)return nil, nil
}
这个文件不会参与编译,只是为了告诉 Wire 各个组件的依赖关系,以及期望的生成结果。在这个文件:我们期望 Wire 生成一个返回 App
实例或 error
的 CreateApp
函数,App
实例初始化所需要的全部依赖都由 ProviderSet
这个组件列表提供,而 ProviderSet
声明了所有可能需要的组件的获取/初始化方法,也暗示组件之间的依赖顺序。
组件的获取/初始化方法,在 Wire 中叫做“组件的 provider”
还有几点需要注意:
-
wire.Build
的作用是 连接或绑定我们之前定义的所有初始化函数。当我们运行wire
工具来生成代码时,它就会根据这些依赖关系来自动创建和注入所需的实例。文件首行必须加上
//go:build wireinject
或// +build wireinject
(go 1.18
之前的版本使用) 注释,作用是只有在使用wire
工具时才会编译这部分代码,其他情况下忽略。 - 在这个文件中,编辑器和 IDE 可能无法提供代码提示,但没关系,稍后会介绍如何解决这个问题
- 其中
CreateApp
的返回(两个 nil)没有任何意义,只是为了兼容 Go 语法。
2.生成初始化代码
命令行执行 wire ./...
,然后就能得到下面这个自动生成的代码文件。
cmd/web/wire_gen.go
// Code generated by Wire. DO NOT EDIT.//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinjectpackage mainimport "..." // 简化示例func CreateApp() (*App, error) {conf, err := configs.Get()if err != nil {return nil, err}db, err := databases.New(conf)if err != nil {return nil, err}userRepo, err := repositories.NewUser(db)if err != nil {return nil, err}userSvc, err := services.NewUser(userRepo)if err != nil {return nil, err}app, err := NewApp(userSvc)if err != nil {return nil, err}return app, nil
}
3.使用初始化代码
Wire 已经帮我们生成了真正的 CreateApp
初始化方法,现在可以直接使用它。
cmd/web/main.go
// main.go
func main() {app := CreateApp()app.Run()
}
组件按需加载
Wire 有个优雅的特点,不管在 wire.Build
中传入了多少个组件的 provider,Wire 始终只会按照实际需要来初始化组件,所有不需要的组件都不会生成相应的初始化代码。
因此,我们在使用时可以尽可能地提供更多的 provider,把挑选组件的工作交给 Wire。这样我们在开发时不管引用新组件、还是弃用老组件,都不需要修改初始化步骤的代码 wire.go。
比如,可以把 services 层中所有的实例构造器都提供出去。
pkg/services/wire.go
package services// 提供了所有 service 的实例构造器
var ProviderSet = wire.NewSet(NewUserService, NewFeedService, NewSearchService, NewBannerService)
在初始化中,尽可能地引用所有可能需要的组件 provider。
cmd/web/wire.go
var ProviderSet = wire.NewSet(configs.ProviderSet,databases.ProviderSet,repositories.ProviderSet,services.ProviderSet, // 引用了所有 service 的实例构造器NewApp,
)func CreateApp() (*App, error) {wire.Build(ProviderSet) // wire 会按照实际需要,选择性地进行初始化return nil, nil
}
Wire 的核心概念
Wire
有两个核心概念:提供者(providers
)和注入器(injectors
)。
Wire 提供者(providers)
提供者:一个可以产生值的函数,也就是有返回值的函数。例如入门代码里的 NewPostHandler
函数:
func NewPostHandler(serv service.IPostService) *PostHandler {return &PostHandler{serv: serv}
}
复制
返回值不仅限于一个,如果有需要的话,可以额外添加一个 error
的返回值。
如果提供者过多的时候,我们还可以以分组的形式进行连接,例如将 post
相关的 handler
和 service
进行组合:
package handlervar PostSet = wire.NewSet(NewPostHandler, service.NewPostService)
复制
使用 wire.NewSet
函数将提供者进行分组,该函数返回一个 ProviderSet
结构体。不仅如此,wire.NewSet
还能对多个 ProviderSet
进行分组 `wire.NewSet(PostSet, XxxSet)
`
对于之前的 InitializeApp
函数,我们可以这样升级:
//go:build wireinjectpackage wirefunc InitializeAppV2() *gin.Engine {wire.Build(handler.PostSet,ioc.NewGinEngineAndRegisterRoute,)return &gin.Engine{}
}
然后通过 Wire
命令生成代码,和之前的结果一致。
Wire 注入器(injectors)
注入器(injectors
)的作用是将所有的提供者(providers
)连接起来,回顾一下我们之前的代码:
func InitializeApp() *gin.Engine {wire.Build(handler.NewPostHandler,service.NewPostService,ioc.NewGinEngineAndRegisterRoute,)return &gin.Engine{}
}
InitializeApp
函数就是一个注入器,函数内部通过 wire.Build
函数连接所有的提供者,然后返回 &gin.Engine{}
,该返回值实际上并没有使用到,只是为了满足编译器的要求,避免报错而已,真正的返回值来自 ioc.NewGinEngineAndRegisterRoute
。
参考
Go 项目必备:深入浅出 Wire 依赖注入工具-腾讯云开发者社区-腾讯云
这篇关于Go 项目必备:深入浅出 Wire 依赖注入工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!