Wire 是一个轻巧的Golang依赖注入工具。它由Go Cloud团队开发,通过自动生成代码的方式在编译期完成依赖注入。
依赖注入是保持软件 “低耦合、易维护” 的重要设计准则之一。
此准则被广泛应用在各种开发平台之中,有很多与之相关的优秀工具。
其中最著名的当属 Spring,Spring IOC 作为框架的核心功能对Spring的发展到今天统治地位起了决定性作用。
依赖注入很重要,所以Golang社区中早已有人开发了相关工具, 比如来自Uber 的 dig 、来自Facebook 的 inject 。他们都通过反射机制实现了运行时依赖注入。
安装很简单,运行 go get github.com/google/wire/cmd/wire
之后, wire 命令行工具 将被安装到 $GOPATH/bin
。只要确保 $GOPATH/bin
在 $PATH 中, wire 命令就可以在任何目录调用了。安装成功后运行如下命令
设计一个程序,其中 Event依赖Greeter,Greeter依赖Message
package main
import (
"fmt"
"github.com/pkg/errors"
"time"
)
type Message string
func NewMessage(phrase string) Message {
return Message(phrase)
}
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, 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)
}
如果运行Event需要逐个构建依赖,代码如下
func main() {
message := NewMessage("lisus2000")
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
在此之前先介绍两个概念
Provider
Provider 你可以把它理解成工厂函数,这个函数的入参是依赖的属性,返回值为新一个新的类型实例
如下所示都是 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。
func NewMessage() Message {
return Message("Hi there!")
}
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
Injector
我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系
如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
编写wire.go文件
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
var wireSet = wire.NewSet(wire.Struct(new(Greeter), "Message"), NewMessage)
func InitializeEvent(phrase string) (Event, error) {
//我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系
//如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,
//代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理
panic(wire.Build(NewEvent, wireSet))
//return Event{}, nil
}
注:使用wire生成代码时,要在代码上加以下
可以在wire.go第一行加入 //+build wireinject (与//go:build wireinject等效)注释,确保了这个文件在我们正常编译的时候不会被引用
在带有wire.go目录下运行wire命令,就会生成wire_gen.go文件,如下图所示
完整的main.go文件如下
package main
import (
"fmt"
"github.com/pkg/errors"
"time"
)
type Message string
func NewMessage(phrase string) Message {
return Message(phrase)
}
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, 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)
}
func main() {
event, _ := InitializeEvent("hello")
event.Start()
}
wire的进阶,详见https://blog.csdn.net/weixin_50071922/article/details/133278161
func NewGoodsAppWire(logOpts *log.Options, registrar registry.Registrar, serverOpts *options.ServerOptions,
rpcServer *rpcserver.Server) (*gapp.App, error) {
//初始化log
log.Init(logOpts)
defer log.Flush()
return gapp.New(
gapp.WithName(serverOpts.Name),
gapp.WithRPCServer(rpcServer),
gapp.WithRegistrar(registrar),
), nil
}
func NewRegistrar(registry *options.RegistryOptions) registry.Registrar {
c := api.DefaultConfig()
c.Address = registry.Address
c.Scheme = registry.Scheme
client, err := api.NewClient(c)
if err != nil {
panic(err)
}
return consul.New(client, consul.WithHealthCheck(true))
}
func NewGoodsRPCServerWire(telemetry *options.TelemetryOptions,
serverOpts *options.ServerOptions, gserver gpb.GoodsServer) (*rpcserver.Server, error) {
// 初始化 open-telemetry 的 exporter
trace.InitAgent(trace.Options{
Name: telemetry.Name,
Endpoint: telemetry.Endpoint,
Sampler: telemetry.Sampler,
Batcher: telemetry.Batcher,
})
// 这里会根据 endpoint 为单元注册 trace 服务的实例
rpcAddr := fmt.Sprintf("%s:%d", serverOpts.Host, serverOpts.Port)
var opts []rpcserver.ServerOption
opts = append(opts, rpcserver.WithAddress(rpcAddr))
if serverOpts.EnableLimit {
opts = append(opts, rpcserver.WithUnaryInterceptor(grpc.NewUnaryServerInterceptor()))
}
grpcServer := rpcserver.NewServer(opts...)
gpb.RegisterGoodsServer(grpcServer.Server, gserver)
return grpcServer, nil
}
func NewGoodsServerWire(srv v1.ServiceFactory) proto.GoodsServer {
return &goodsServer{
srv: srv,
}
}
func NewGoodsServiceWire(data v1.DataFactory, dataSearch v12.SearchFactory) ServiceFactory {
return &service{
data: data,
dataSearch: dataSearch,
}
}
func GetDBFactoryOr(mysqlOpts *options.MySQLOptions) (v1.DataFactory, error) {
if mysqlOpts == nil && dbFactory == nil {
return nil, errors.WithCode(code.ErrConnectDB, "failed to get mysql store factory")
}
var err error
once.Do(func() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlOpts.Username,
mysqlOpts.Password,
mysqlOpts.Host,
mysqlOpts.Port,
mysqlOpts.Database)
//希望大家自己可以去封装logger
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.LogLevel(mysqlOpts.LogLevel), // 日志级别
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: false, // 禁用彩色打印
},
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
return
}
sqlDB, _ := db.DB()
dbFactory = &mysqlFactory{
db: db,
}
//允许连接多少个mysql
sqlDB.SetMaxOpenConns(mysqlOpts.MaxOpenConnections)
//允许最大的空闲的连接数
sqlDB.SetMaxIdleConns(mysqlOpts.MaxIdleConnections)
//重用连接的最大时长
sqlDB.SetConnMaxLifetime(mysqlOpts.MaxConnectionLifetime)
})
if dbFactory == nil || err != nil {
return nil, errors.WithCode(code.ErrConnectDB, "failed to get mysql store factory")
}
return dbFactory, nil
}
func GetSearchFactoryOr(opts *options.EsOptions) (v12.SearchFactory, error) {
if opts == nil && searchFactory == nil {
return nil, errors.New("failed to get es client")
}
once.Do(func() {
esOpt := db.EsOptions{
Host: opts.Host,
Port: opts.Port,
}
esClient, err := db.NewEsClient(&esOpt)
if err != nil {
return
}
searchFactory = &dataSearch{
esClient: esClient,
}
})
if searchFactory == nil {
return nil, errors.New("failed to get es client")
}
return searchFactory, nil
}
//go:build wireinject
// +build wireinject
package srv
import (
"github.com/google/wire"
v1 "mxshop/app/goods/srv/internal/controller/v1"
"mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
"mxshop/app/goods/srv/internal/data/v1/db"
v12 "mxshop/app/goods/srv/internal/service/v1"
"mxshop/app/pkg/options"
gapp "mxshop/gmicro/app"
"mxshop/pkg/log"
)
func initApp(*log.Options, *options.ServerOptions, *options.RegistryOptions,
*options.TelemetryOptions, *options.MySQLOptions, *options.EsOptions) (*gapp.App, error) {
wire.Build(NewGoodsAppWire,
NewRegistrar,
NewGoodsRPCServerWire,
v1.NewGoodsServerWire,
v12.NewGoodsServiceWire,
db.GetDBFactoryOr,
es.GetSearchFactoryOr,
)
return &gapp.App{}, nil
}
3.3生成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 srv
import (
v1_2 "mxshop/app/goods/srv/internal/controller/v1"
"mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
"mxshop/app/goods/srv/internal/data/v1/db"
"mxshop/app/goods/srv/internal/service/v1"
"mxshop/app/pkg/options"
"mxshop/gmicro/app"
"mxshop/pkg/log"
)
// Injectors from wire.go:
func initApp(logOptions *log.Options, serverOptions *options.ServerOptions, registryOptions *options.RegistryOptions, telemetryOptions *options.TelemetryOptions, mySQLOptions *options.MySQLOptions, esOptions *options.EsOptions) (*app.App, error) {
registrar := NewRegistrar(registryOptions)
dataFactory, err := db.GetDBFactoryOr(mySQLOptions)
if err != nil {
return nil, err
}
searchFactory, err := es.GetSearchFactoryOr(esOptions)
if err != nil {
return nil, err
}
serviceFactory := v1.NewGoodsServiceWire(dataFactory, searchFactory)
goodsServer := v1_2.NewGoodsServerWire(serviceFactory)
server, err := NewGoodsRPCServerWire(telemetryOptions, serverOptions, goodsServer)
if err != nil {
return nil, err
}
appApp, err := NewGoodsAppWire(logOptions, registrar, serverOptions, server)
if err != nil {
return nil, err
}
return appApp, nil
}
在如下方法替换接口
运行main.go文件,如下显示,正常启动