在Golang中依赖注入-wire篇

news2024/10/7 18:22:58

文章目录

  • 一、依赖注入是什么?
  • 二、安装
    • 1.快速入门
    • 2.两个概念
    • 3.使用wire生成代码
    • 4.进阶用法
  • 总结


一、依赖注入是什么?

有时候一个结构体非常复杂,包含了非常多各种类型的属性,这些属性又包含了更多的属性,当我们创建这样一个结构体时需要编写大量的代码。面向接口编程可以让我们的代码避免耦合更具扩展性,但统一更换接口实现时需要大范围的修改代码。

依赖注入帮助我们解决类似的问题,依赖注入框架能够自动解析依赖关系,帮助我们自动构建结构体实例。依赖注入可以对接口注入实例,让整个代码系统不用关注具体的接口实现。

由于Go语言静态的特性,依赖注入在Go中应用并不广泛,主要有两种实现方式:代码生成和反射。

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

二、安装

如上所述,wire利用代码生成来实现依赖注入,所以我们需要安装wire命令到PATH中

go install github.com/google/wire/cmd/wire@latest

执行wire -h 有如下显示说明安装成功

$ wire -h
Usage: wire <flags> <subcommand> <subcommand args>

Subcommands:
 check            print any Wire errors found
 commands         list all command names
 diff             output a diff between existing wire_gen.go files and what gen would generate
 flags            describe all known top-level flags
 gen              generate the wire_gen.go file for each package
 help             describe subcommands and their syntax
 show             describe all top-level provider sets


Use "wire flags" for a list of top-level flags

1.快速入门

设计一个程序,其中 Event依赖Greeter,Greeter依赖Message

type Message string

func NewMessage() Message {
 return Message("Hi there!")
}

type Greeter struct {
 Message Message
}

func NewGreeter(m Message) Greeter {
 return Greeter{Message: m}
}

func (g Greeter) Greet() Message {
 return g.Message
}

type Event struct {
 Greeter Greeter // <- adding a Greeter field
}

func NewEvent(g Greeter) Event {
 return Event{Greeter: g}
}

func (e Event) Start() {
 msg := e.Greeter.Greet()
 fmt.Println(msg)
}

在这里插入图片描述
如果运行Event需要逐个构建依赖,代码如下

func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}

2.两个概念

正式开始前需要先了解一下 wire 当中的两个概念:provider 和 injector

Provider
Provider 你可以把它理解成工厂函数,这个函数的入参是依赖的属性,返回值为新一个新的类型实例

如下所示都是 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。

func NewMessage() Message {
 return Message("Hi there!")
}

func NewGreeter(m Message) Greeter {
 return Greeter{Message: m}
}

不过需要注意的是在 wire 中不能存在两个 provider 返回相同的组件类型。即如下两个函数不能同时存在

func NewMessage1() Message {
 return Message("Hi there!")
}

func NewMessage2() Message {
 return Message("Hi there!")
}

Injector

我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系

如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理

// wire.go
func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

3.使用wire生成代码

当准备好provider 和 injector之后,通过 wire 命令可以自动生成一个完整的函数。如果 wire.go 不再当前路径下,也可以指定包名

# 等价 wire ./internal 
$ wire gen ./internal
wire: github.com/liangwt/note/golang/demo/wire/internal: wrote /golang/demo/wire/internal/wire_gen.go

在执行完wire命令之后就会生成wire_gen.go,它的内容就是构建 Event。我们需要连同wire_gen.go一起提交到版本控制系统里

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package internal

// Injectors from wire.go:

func InitializeEvent() Event {
 message := NewMessage()
 greeter := NewGreeter(message)
 event := NewEvent(greeter)
 return event
}

此时我们就可以直接在main里这样用,省去逐个构建依赖的麻烦

func main() {
 event := internal.InitializeEvent()
 event.Start()
}

小技巧

可以在wire.go第一行加入 //+build wireinject (与//go:build wireinject等效)注释,确保了这个文件在我们正常编译的时候不会被引用

而 wire . 生成的文件 wire_gen.go 会包含 //+build !wireinject 注释,正常编译的时候,不指定 tag 的情况下会引用这个文件

//go:build wireinject
// +build wireinject

// wire.go
package internal

import "github.com/google/wire"

func InitializeEvent() Event {
 wire.Build(NewEvent, NewGreeter, NewMessage)
 return Event{}
}

4.进阶用法

返回错误

在Go中如果遇到错误,我们会在最后一个返回值返回error,wire同样也支持返回错误的情况,只需要在 injector的函数签名中加上error返回值即可

调整provider的签名

type Event struct {
 Greeter Greeter
}

//func NewEvent(g Greeter) Event {
// return Event{Greeter: g}
//}
func NewEvent(g Greeter) (Event, error) {
 if time.Now().Unix()%2 == 0 {
  return Event{}, errors.New("could not create event: event greeter is grumpy")
 }

 return Event{Greeter: g}, nil
}

func (e Event) Start() {
 msg := e.Greeter.Greet()
 fmt.Println(msg)
}

调整injector的签名

// wire.go
func InitializeEvent() (Event, error) {
 panic(wire.Build(NewEvent, NewGreeter, NewMessage))
}

生成的代码如下所示,可以发现会像我们自己写代码一样判断一下 if err 然后返回

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

// Injectors from wire.go:

func InitializeEvent() (Event, error) {
 message := NewMessage()
 greeter := NewGreeter(message)
 event, err := NewEvent(greeter)
 if err != nil {
  return Event{}, err
 }
 return event, nil
}

main中的调用

func main() {
    e, err := InitializeEvent()
    if err != nil {
        fmt.Printf("failed to create event: %s\n", err)
        os.Exit(2)
    }
    e.Start()
}

传入参数

如果要构建的目标组件需要外部的输入时,可以在定义provider 和 injector时同步加上输入

调整provider的签名

// provider.go
type Message string

func NewMessage(phrase string) Message {
 return Message(phrase)
}

调整injector的签名

// wire.go
func InitializeEvent(phrase string) (Event, error) {
 wire.Build(NewEvent, NewGreeter, NewMessage)
}

不展示生成的代码了,main中的调用

func main() {
 event, err := InitializeEvent("Hi there!")
 if err != nil {
  fmt.Printf("failed to create event: %s\n", err)
  os.Exit(2)
 }

 event.Start()
}

ProviderSet

有时候可能多个类型有相同的依赖,我们每次都将相同的构造器传给wire.Build()不仅繁琐,而且不易维护,一个依赖修改了,所有传入wire.Build()的地方都要修改。为此,wire提供了一个ProviderSet(构造器集合),可以将多个构造器打包成一个集合,后续只需要使用这个集合即可。

如下,后续再调整NewGreeter和NewMessage,就可以统一改了

// wire.go

var wireSet = wire.NewSet(NewGreeter, NewMessage)

func InitializeEvent(phrase string) (Event, error) {
 wire.Build(wireSet, NewEvent)
}

结构构造器

对于Greeter,前文使用NewGreeter作为provider

type Greeter struct {
 Message Message
}

func NewGreeter(m Message) Greeter {
 return Greeter{Message: m}
}
}

如果不显式实现NewGreeter,可以直接使用wire提供的结构构造器(Struct Provider)。结构构造器创建某个类型的结构,然后用参数或调用其它构造器填充它的字段

结构构造器使用wire.Struct函数,第一个参数固定为new(结构名),后面可接任意多个参数,表示需要为该结构的哪些字段注入值

我们也可以使用通配符*表示注入所有字段

如下的例子代表Greeter需要注入Message字段,不用再单独实现NewGreeter了

Greeter
var wireSet = wire.NewSet(NewMessage, wire.Struct(new(Greeter), "Message"))

func InitializeEvent(phrase string) (Event, error) {
 wire.Build(wireSet, NewEvent)
}

结构字段作为构造器

有时候我们编写一个构造器,只是简单的返回某个结构的一个字段,这时可以使用wire.FieldsOf简化操作。

type Foo struct {
    S string
    N int
    F float64
}

func getS(foo Foo) string {
    // Bad! Use wire.FieldsOf instead.
    return foo.S
}

func provideFoo() Foo {
    return Foo{ S: "Hello, World!", N: 1, F: 3.14 }
}

func injectedMessage() string {
    wire.Build(
        provideFoo,
        getS)
    return ""
}
func injectedMessage() string {
    wire.Build(
        provideFoo,
        wire.FieldsOf(new(Foo), "S"))
    return ""
}

同样的,第一个参数为new(结构名),后面跟多个参数表示将哪些字段作为构造器,*表示全部。

绑定值

有时候,我们需要为某个类型绑定一个值,而不想依赖构造器每次都创建一个新的值。有些类型天生就是单例,例如配置,数据库对象(sql.DB)。这时我们可以使用wire.Value绑定值,使用wire.InterfaceValue绑定接口

修改一下 Greeter 使他依赖一个int和io.Reader然后为它直接绑定 a=10 、io.Reader = os.Stdin

// main.go
var singletonMessage = NewMessage("Hello, world!")

type Message string

func NewMessage(phrase string) Message {
 return Message(phrase)
}

type Greeter struct {
 a int
 r io.Reader

 Message Message
}
var wireSet = wire.NewSet(
 wire.Struct(new(Greeter), "*"),
 wire.Value(10),
 wire.InterfaceValue(new(io.Reader), os.Stdin),
 wire.Value(singletonMessage),
)

func InitializeEvent(phrase string) (Event, error) {
 panic(wire.Build(wireSet, NewEvent))
}

绑定接口
使用 wire.Bind 将 Struct 和接口进行绑定,表示这个结构体实现了这个接口,wire.Bind 的使用方法就是 wire.Bind(new(接口), new(实现))

func NewGreeter(m Message, phrase string) Greeter {
 return Greeter{Message: m}
}

func (g Greeter) Greet() Message {
 return g.Message
}

type IGreeter interface{
 Greet() Message
}

type Event struct {
 Greeter IGreeter
}
var wireSet = wire.NewSet(
 wire.Struct(new(Greeter), "*"),
 wire.Value(10),
 wire.InterfaceValue(new(io.Reader), os.Stdin),
 wire.Value(singletonMessage),
)

func InitializeEvent(phrase string) (Event, error) {
 panic(wire.Build(wireSet, NewEvent, wire.Bind(new(IGreeter), new(*Greeter))))
}

总结

本文介绍依赖注入框架wire的基础用法:

  • 实现各struct的工厂函数,wire称之为provider
  • 在wire.go中利用函数签名和函数体中调用wire.Build描述一个struct的所有依赖,此函数被称为 injector
  • 执行wire命令,会生成wire_gen.go 其中包含和injector签名相同的函数,函数的内容为构建的相关依赖并组合
  • 使用wire_gen.go中的函数创建我们的实例

除了基础用法之外,还有很多高级用法,例如绑定接口、绑定值,来实现面向接口编程和单例模式,此部分可以参考官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1039657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全CTF比赛有哪些事?——《CTF那些事儿》告诉你

目录 前言 一、内容简介 二、读者对象 三、专家推荐 四、全书目录 前言 CTF比赛是快速提升网络安全实战技能的重要途径&#xff0c;已成为各个行业选拔网络安全人才的通用方法。但是&#xff0c;本书作者在从事CTF培训的过程中&#xff0c;发现存在几个突出的问题&#xff1…

8年测试老鸟亲述,软件测试工程师最核心的竞争力到底是什么?

前言 无论从事哪一个行业&#xff0c;核心竞争力都是绕不开的一个话题&#xff0c;提高核心竞争力是我们一生中的重要课题。它保障了我们不会被替代&#xff0c;即在竞争中别人都争不过你&#xff0c;只有你才做得到的某种能力。 对于测试员而言&#xff0c;究竟何为这个岗位…

【算法专题突破】二分查找 - x 的平方根(18)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; 这道题就是求算数平方根&#xff0c; 要注意的点是他只需要保留整数部分&#xff0c;小数部分会舍去 2. 算法…

C++实现nms和softmax

最近在面试过程中遇到了手写nms的问题&#xff0c;结束后重新实现并调通了nms和softmax的代码。 1、NMS 原理&#xff08;通俗易懂&#xff09;&#xff1a; 先假设有6个候选框&#xff0c;根据分类器类别分类概率做排序&#xff0c;从小到大分别属于车辆的概率分别为A、B、C、…

【计算机视觉】2.图像特征提取

图像特征提取 一、颜色特征量化颜色直方图聚类颜色直方图 二、边缘特征边缘边缘定义边缘提取边缘精细 三、特征点的特征描述子Harris角点FAST角点斑点SIFTHaar-like特征SURFORBLBPGabor 一、颜色特征 量化颜色直方图 HSV空间 优势&#xff1a;计算高效 劣势&#xff1a;量化问…

LNK1123: 转换到 COFF 期间失败: 文件无效或损坏

复制系统目录下面的&#xff1a;cvtres.exe到vs2010安装目录下面即可。

图神经网络详细内容

文章目录 1. 图神经网络1.1 GCN图卷积网络1.1.1 计算过程1.1.2 公式的物理原理1.1.3 GCN代码实现 1.2 GAT图注意力网络1.2.1 计算过程与原理1.2.2 GAT代码实现 1.3 消息传递1.4 图采样介绍1.5 图采样算法&#xff1a;GraphSAGE1.6 图采样算法&#xff1a;PinSAGE 2. 参考 1. 图…

数据结构 | 树

树 树是n&#xff08;n>0&#xff09;个结点的有限集。当n 0时&#xff0c;称为空树。在任意一棵非空树中应满足&#xff1a; 有且仅有一个特定的称为根的结点。当n>1时&#xff0c;其余节点可分为m&#xff08;m>0&#xff09;个互不相交的有限集T1,T2,…,Tm&#…

基于SpringBoot的可以做毕设或者课设的实时聊天系统(仿微信)

技术栈 前后端分离前端使用: Vue Element后端使用: SpringBoot Mysql8.0 Mybatis WebSocket 功能 登录和注册页 登录 和 注册 修改个人信息页 修改个人信息 消息列表页 展示最近半年的聊天信息&#xff0c;删除聊天记录 搜索好友和群页 搜索JJ号来找到 群/好友 好友信息详情页…

一文搞懂 this 指向

目录 一、前言二、箭头函数三、new指向四、bind五、call和apply六、bind call apply区别七、对象&#xff08;obj.&#xff09;八、全局this指向九、不在函数里 一、前言 JS 中 this 指向问题 - 掘金 在JavaScript中&#xff0c;this关键字表示当前执行代码的上下文对象。它的…

Linux 系统死机后挽救措施

一、背景 因我们日常使用Linux系统过程中&#xff0c;会不时遇到系统崩溃的事&#xff0c;但这时系统界面除了呈现一片告警字符外&#xff0c;无发执行任何其他操作&#xff0c;留给我们的要不重启&#xff0c;要不就是尴尬等待指令。那面对会这种情况&#xff0c;还到底有没有…

人工智能热潮推动光芯片与光器件需求飙升

随着人工智能技术的迅猛发展&#xff0c;光芯片和光器件作为关键的基础技术&#xff0c;在这一浪潮下迎来了前所未有的需求增长。光芯片和光器件的高速率、高带宽、低能耗等优势&#xff0c;使其在人工智能应用中发挥着重要作用&#xff0c;正日益成为推动人工智能进步的关键要…

上海长宁来福士P2.5直径4米无边圆形屏圆饼屏圆面屏圆盘屏平面圆屏异形创意LED显示屏案例

长宁来福士广场是一个大型广场&#xff0c;坐落于上海中山公园商圈的核心区域&#xff0c;占地逾6万平方米&#xff0c;其中地上总建筑面积近24万平方米&#xff0c;总投资额约为96亿人民币。 LED圆形屏是根据现场和客户要求定制的一款异形创意LED显示屏&#xff0c;进行文字、…

513找树左下角值

给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7class Solution { public:int findBottomLeftValue(TreeNode…

RDLC动态设置整个表格是否显示

最近有个新的需求&#xff1a;使用RDLC打印&#xff0c;当数据库中能查出数据时&#xff0c;显示表格。没有数据时&#xff0c;不显示整个表格。 1.首先在RDLC中选中表格的任意一列&#xff0c;右键Tablix属性 2.Tablix属性中选中可见性》选中基于表达式显示或隐藏(E)并点开右…

Word | 简单可操作的快捷公式编号、右对齐和引用方法

1. 问题描述 在理工科论文的写作中&#xff0c;涉及到大量的公式输入&#xff0c;我们希望能够按照章节为公式进行编号&#xff0c;并且实现公式居中&#xff0c;编号右对齐的效果。网上有各种各样的方法来实现&#xff0c;操作繁琐和简单的混在一起&#xff0c;让没有接触过公…

深度强化学习(三)马尔科夫决策过程

文章目录 马尔可夫过程MP马尔科夫链MC状态转移概率矩阵n步转移概率 马尔科夫链 马尔科夫奖励过程MRP奖励机制计算价值概念定义计算价值推导贝尔曼方程贝尔曼方程实际应用 参考文章&#xff1a;https://blog.csdn.net/taka_is_beauty/article/details/88356375 序贯决策问题是针…

【Vue3 源码解析】nextTick

nextTick 是 Vue 3 中用于异步执行回调函数的函数&#xff0c;它会将回调函数延迟到下一个微任务队列中执行。其中&#xff0c;Vue 更新 DOM 是异步的。下面是对 nextTick 函数的详细解释&#xff1a; export function nextTick<T void, R void>(this: T,fn?: (this:…

2023-09-25 LeetCode每日一题(LFU 缓存)

2023-09-25每日一题 一、题目编号 460. LFU 缓存二、题目链接 点击跳转到题目位置 三、题目描述 请你为 最不经常使用&#xff08;LFU&#xff09;缓存算法设计并实现数据结构。 实现 LFUCache 类&#xff1a; LFUCache(int capacity) - 用数据结构的容量 capacity 初始…

全流量安全分析之服务器非法外连

服务器非法外连监控的重要性在于保护服务器的安全性和防止被黑客利用&#xff0c;以下是几个重要的理由&#xff1a; 1、发现恶意活动&#xff1a;通过监控服务器的外连流量&#xff0c;可以及时发现是否有未经授权或可疑的连接尝试。这可能包括入侵攻击、数据泄露、恶意软件传…