Go 项目必备:深入浅出 Wire 依赖注入工具

2023-11-10 18:30

本文主要是介绍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 相关的 handlerservice 进行组合:

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 依赖注入工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/384488

相关文章

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加

解决idea启动项目报错java: OutOfMemoryError: insufficient memory

《解决idea启动项目报错java:OutOfMemoryError:insufficientmemory》:本文主要介绍解决idea启动项目报错java:OutOfMemoryError... 目录原因:解决:总结 原因:在Java中遇到OutOfMemoryError: insufficient me

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

python项目环境切换的几种实现方式

《python项目环境切换的几种实现方式》本文主要介绍了python项目环境切换的几种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 如何在不同python项目中,安装不同的依赖2. 如何切换到不同项目的工作空间3.创建项目

SpringBoot项目整合Netty启动失败的常见错误总结

《SpringBoot项目整合Netty启动失败的常见错误总结》本文总结了SpringBoot集成Netty时常见的8类问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一、端口冲突问题1. Tomcat与Netty端口冲突二、主线程被阻塞问题1. Netty启动阻

python项目打包成docker容器镜像的两种方法实现

《python项目打包成docker容器镜像的两种方法实现》本文介绍两种将Python项目打包为Docker镜像的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录简单版:(一次成功,后续下载对应的软件依赖)第一步:肯定是构建dockerfile,如下:第二步

Go语言实现桥接模式

《Go语言实现桥接模式》桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化,本文就来介绍一下了Go语言实现桥接模式,感兴趣的可以了解一下... 目录简介核心概念为什么使用桥接模式?应用场景案例分析步骤一:定义实现接口步骤二:创建具体实现类步骤三:定义抽象类步骤四:创建扩展抽象类步

GO语言实现串口简单通讯

《GO语言实现串口简单通讯》本文分享了使用Go语言进行串口通讯的实践过程,详细介绍了串口配置、数据发送与接收的代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录背景串口通讯代码代码块分解解析完整代码运行结果背景最近再学习 go 语言,在某宝用5块钱买了个