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

相关文章

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

idea+spring boot创建项目的搭建全过程

《idea+springboot创建项目的搭建全过程》SpringBoot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目,:本文主要介绍idea+springb... 目录一.idea四种搭建方式1.Javaidea命名规范2JavaWebTomcat的安装一.明确tomcat

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

Go语言中json操作的实现

《Go语言中json操作的实现》本文主要介绍了Go语言中的json操作的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、jsOChina编程N 与 Go 类型对应关系️ 二、基本操作:编码与解码 三、结构体标签(Struc

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni

python依赖管理工具UV的安装和使用教程

《python依赖管理工具UV的安装和使用教程》UV是一个用Rust编写的Python包安装和依赖管理工具,比传统工具(如pip)有着更快、更高效的体验,:本文主要介绍python依赖管理工具UV... 目录前言一、命令安装uv二、手动编译安装2.1在archlinux安装uv的依赖工具2.2从github

MyCat分库分表的项目实践

《MyCat分库分表的项目实践》分库分表解决大数据量和高并发性能瓶颈,MyCat作为中间件支持分片、读写分离与事务处理,本文就来介绍一下MyCat分库分表的实践,感兴趣的可以了解一下... 目录一、为什么要分库分表?二、分库分表的常见方案三、MyCat简介四、MyCat分库分表深度解析1. 架构原理2. 分

深入浅出Java中的Happens-Before核心规则

《深入浅出Java中的Happens-Before核心规则》本文解析Java内存模型中的Happens-Before原则,解释其定义、核心规则及实际应用,帮助理解多线程可见性与有序性问题,掌握并发编程... 目录前言一、Happens-Before是什么?为什么需要它?1.1 从一个问题说起1.2 Haht