依赖注入工具代码生成器 wire

VSole2022-08-04 11:06:38

简介 

wire是一个代码生成工具,它通过自动生成代码的方式完成依赖注入。

应用场景

wire作为依赖注入的代码生成工具,非常适合复杂对象的创建。而在大型项目中,拥有一个合适的依赖注入的框架将使得项目的开发与维护十分便捷。

Wire核心概念

wire 中最核心的两个概念就是Injector和Provider。

Provider : 生成组件的普通方法。这些方法接收所需依赖作为参数,创建组件并将其返回

Injector : 代表了我们最终要生成的构建函数的函数签名,返回值代表了构建的目标,在最后生成的代码中,此函数签名会完整的保留下来。

安装

    go get github.com/google/wire/cmd/wire

 代码生成

命令行在指定目录下执行 wire命令即可。

示例学习

https://github.com/google/wire/tree/main/internal/wire/testdata/

 成员介绍

func NewSet(...interface{}) ProviderSet
func Build(...interface{}) string
func Bind(iface, to interface{}) Binding
func Struct(structType interface{}, fieldNames ...string) StructProvider
func FieldsOf(structType interface{}, fieldNames ...string) StructFields
func Value(interface{}) ProvidedValue
func InterfaceValue(typ interface{}, x interface{}) ProvidedValue

基础代码

main.go

package main
type Leaf struct {
    Name string
}
type Branch struct{
    L Leaf
}
type Root struct {
    B Branch
}
func NewLeaf(name string) Leaf {return Leaf{Name:name}}
func NewBranch(l Leaf) Branch {return Branch{L:l}}
func NewRoot(b Branch) Root {return Root{B:b}}

wire.go

// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
    "github.com/google/wire"
)
func InitRoot(name string) Root {
    wire.Build(NewLeaf,NewBranch,NewRoot)
    return Root{}
}

wire_gen.go

// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func InitRoot(name string) Root {
 leaf := NewLeaf(name)
 branch := NewBranch(leaf)
 root := NewRoot(branch)
 return root
}

这里我们可以看到代码的生成是根据wire.Build参数的输入与输出类型来决定的。

wire.Build的参数是Provider的不定长列表。

wire包成员的作用

wire的成员每一个都是为了Provider服务的,他们各自有适用的场景。

NewSet 

NewSet的作用是为了防止Provider过多导致混乱,它把一组业务相关的Provider放在一起组织成ProviderSet。

wire.go可以写成

var NewBranchSet = wire.NewSet(NewLeaf,NewBranch)
func InitRoot(name string) Root {
    wire.Build(NewBranchSet,NewRoot)
    return Root{}
}

值得注意的事,NewSet可以写在原结构体所在的文件中,以方便切换和维护。

Bind

Bind函数的作用是为了让接口类型参与wire的构建过程。wire的构建依靠的是参数的类型来组织代码,所以接口类型天然是不支持的。Bind函数通过将接口类型和实现类型绑定,来达到依赖注入的目的。

type Fooer interface{
    HelloWorld() 
}
type Foo struct{}
func (f Foo)HelloWorld(){}
var bind = wire.Bind(new(Fooer),new(Foo))

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/BindInjectorArgPointer

这样将bind传入NewSet或Build中就可以将Fooer接口和Foo类型绑定。

这里需要特别注意,如果是*Foo实现了Fooer接口,需要将最后的new(Foo)改成new(*Foo)

Struct

Struct函数用于简化结构体的Provider,当结构体的Provider仅仅是字段赋值时可以使用这个函数。

//当Leaf中成员变量很多时,或者只需要部分初始化时,构造函数会变得很复杂
func NewLeaf(name string) Leaf {return Leaf{Name:name}}
//等价写法
//部分字段初始化
wire.Struct(new(Leaf),"Name")
//全字段初始化
wire.Struct(new(Leaf),"*")

这里的NewLeaf函数可以被下面的部分字段初始化函数替代。

Struct函数可以作为Provider出现在Build或NewSet的参数中。

FieldsOf

FieldsOf函数可以将结构体中的对应字段作为Provider,供wire使用。在上面的代码基础上,我们做如下的等价

//获得Leaf中Name字段的Provider
func NewName(l Leaf) string {return l.Name}
//等价写法
//FieldsOf的方式获得结构体内的字段
wire.FieldsOf(new(Leaf),"Name")

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/FieldsOfStruct

这里的代码是等价的,但是却不能和上面的代码共存,原因稍后会解释。

Value

Value函数为基本类型的属性绑定具体值,在基于需求的基础上简化代码。

func NewLeaf()Leaf{
    return Leaf{
        Name:"leaf",
    }
}
//等价写法
wire.Value(Leaf{Name:"leaf"})

以上两个函数在作为Provider上也是等价的,可以出现在Build或NewSet中。

InterfaceValue

InterfaceValue作用与Value函数类似,只是InterfaceValue函数是为接口类型绑定具体值。

wire.InterfaceValue(new(io.Reader),os.Stdin)

比较少用到,这里就不细讲了。

返回值的特殊情况

返回值 error

wire是支持返回对象的同时携带error的。对于error类型的返回值,wire也能很好的处理。

//main.go
func NewLeaf(name string) (Leaf, error) { return Leaf{Name: name}, nil }
//wire.go
func InitRoot(name string) (Root, error) {
    ...
}
//wire_gen.go
func InitRoot(name string) (Root, error) {
    leaf, err := NewLeaf(name)
    if err != nil {
        return Root{}, err 
    }   
    branch := NewBranch(leaf)
    root := NewRoot(branch)
    return root, nil 
}

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/ReturnError

可以看到当Provider中出现error的返回值时,Injector函数的返回值中也必须携带error的返回值

清理函数CleanUp

清理通常出现在有文件对象,socket对象参与的构建函数中,无论是出错后的资源关闭,还是作为正常获得对象后的析构函数都是有必要的。

清理函数通常作为第二返回值,参数类型为func(),即为无参数无返回值的函数对象。跟error一样,当Provider中的任何一个拥有清理函数,Injector的函数签名返回值中也必须包含该函数类型。

//main.go
func NewLeaf(name string) (Leaf, func()) {
    r := Leaf{Name: name}
    return r, func() { r.Name = "" }
}
func NewBranch(l Leaf) (Branch, func()) { return Branch{L: l}, func() {} }
//wire.go
func InitRoot(name string) (Root, func()) {...}
//wire_gen.go
func InitRoot(name string) (Root, func()) {
    leaf, cleanup := NewLeaf(name)
    branch, cleanup2 := NewBranch(leaf)
    root := NewRoot(branch)
    return root, func() {
        cleanup2()
        cleanup()
    }   
}

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/Cleanup

就这样名为cleanup的清理函数就随着InitRoot返回了。当有多个Provider有cleanup的时候,wire会自动把cleanup加入到最后的返回函数中。

常见问题

类型重复

基础类型

基础类型是构建结构体的基础,其作为参数创建结构体是十分常见的,参数类型重复更是不可避免的。wire通过Go语言语法中的"type A B"的方法来解决词类问题。

//wire.go
type Account string
func InitRoot(name string, account Account) (Root, func()) {...}
出现在wire.go中的"type A B" 会自动复制到wire_gen.go中

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/InjectInput

个人观点 wire着眼于复杂对象的构建,因此基础类型的属性赋值推荐使用结构体本身的Set操作完成。

对象类型重复

每一个Provider都是一个组件的生成方法,如果有两个Provider生成同一类组件,那么在构建过程中就会产生冲突,这里需要特别注意,保证组件的类型唯一性。

循环构建

循环构建指的是多个Provider相互提供参数和返回值形成一个闭环。当wire检查构建的流程含有闭环构建的时候,就会报错。

type Root struct{
    B Branch
}
type Branch struct {
    L Leaf
}
type Leaf struct {
    R Root
}
func NewLeaf(r Root) Leaf {return Leaf{R:r}}
func NewBranch(l Leaf) Branch {return Branch{L:l}}
func NewRoot(b Branch) Root {return Root{B:b}}
...
wire.Build(NewLeaf,NewRranch,NewRoot) //错误 cycle for XXX
...

示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/Cycle

小结

wire是一个强大的工具,它在不运行Go程序的基础上,借助于特定文件("//+build wireinject")的解析,自动生成对象的构造函数代码。

Go语言工程化的过程中,涉及到诸多对象的包级别归类,wire可以很好的协助我们完成复杂对象的构建过程。

函数依赖返回值
本作品采用《CC 协议》,转载必须注明作者和本文链接
okhttp因其稳定、开源、简单易用被众多的app开发人员所使用。okhttp中的execute和enqueue分别为发出同步和异步请求,通过对okhttp发起请求的整个流程进行简单分析就可以快速得到合适的hook点完成对app网络请求的抓包和溯源。
angr是有名的符号执行工具。众所周知,符号执行最大的问题就是状态爆炸。因此,angr中也内置了很多缓解符号执行的策略。本文主要介绍angr里的一些内置机制是如何缓解状态爆炸的。
如果要反汇编成smali,起码要知道这条smali对应的字节码一共几个字节。在确定一条指令占几个字节后,还要知道这几个字节中,谁是操作码,谁是操作数。有了前两步铺垫,最终我们可以解读一条完整的smali的含义。
应用场景wire作为依赖注入的代码生成工具,非常适合复杂对象的创建。而在大型项目中,拥有一个合适的依赖注入的框架将使得项目的开发与维护十分便捷。中最核心的两个概念就是Injector和Provider。这些方法接收所需依赖作为参数,创建组件并将其返回Injector?代码生成命令行在指定目录下执行?
linux跟踪技术之ebpf
2022-12-30 10:51:15
eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行沙盒程序。它可以安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。比如,使用ebpf可以追踪任何内核导出函数的参数,返回值,以实现kernel hook 的效果;通过ebpf,还可以在网络封包到达内核协议栈之前就进行处理,这可以实现流量控制,甚至隐蔽通信。
高达40%的npm包依赖的代码至少包含一个公开漏洞,因此如何解决 Node.js 应用的安全性检测是一个十分重要的问题。
针对现有的静态代码分析工具有较高的误报率与漏报率,提出一种基于切片依赖图(Slice Dependency Graph,SDG)的自动化漏洞检测方法,将程序源代码解析为包含数据依赖和控制依赖信息的切片依赖图,然后使用图神经网络对切片依赖图的结构进行表征学习,最后使用训练的神经网络模型预测待测程序源代码中的漏洞。在 5 类常见缺陷分类(Common Weakness Enumeration,CWE)
源码分析1、LLVM编译器简介LLVM 命名最早源自于底层虚拟机的缩写,由于命名带来的混乱,LLVM就是该项目的全称。LLVM 核心库提供了与编译器相关的支持,可以作为多种语言编译器的后台来使用。自那时以来,已经成长为LLVM的主干项目,由不同的子项目组成,其中许多是正在生产中使用的各种 商业和开源的项目,以及被广泛用于学术研究。
STATEMENT声明由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。雷神众测拥有对此文章的修改和解释权。
它指的是一个有用的工具库,帮助处理和操作XML格式的数据。ROME库允许我们把XML数据转换成Java中的对象,这样我们可以更方便地在程序中操作数据。另外,它也支持将Java对象转换成XML数据,这样我们就可以把数据保存成XML文件或者发送给其他系统。
VSole
网络安全专家