简介
wire 是由 google 开源的一个供 Go 语言使用的依赖注入代码生成工具
。它能够根据你的代码,生成相应的依赖注入 go 代码。
依赖注入
很讨厌依赖注入
这个词,new一个类并使用,硬生生给弄个新名词来衬托好高端
看看各大博客八股文怎么说明
依赖注入是一种设计模式,用于管理对象之间的依赖关系。依赖注入的核心思想是将对象的依赖关系从代码中分离出来,从而使代码更加灵活和可维护。在依赖注入中,对象不再负责创建它所依赖的对象,而是由外部容器来负责创建和管理对象之间的依赖关系。
简单来说,在A类的需要调用另外B、C、D类等,现在A类调用E类即可,E类中统一实例化了B、C、D类
常规类依赖方式
构造函数注入
构造函数注入是最常见的依赖注入方式。在构造函数中,将依赖对象作为参数传递给对象的构造函数
type UserService struct {
userRepository *UserRepository
}
func NewUserService(userRepository *UserRepository) *UserService {
return &UserService{userRepository: userRepository}
}
UserService 依赖于 UserRepository。在 NewUserService 函数中,将 UserRepository 作为参数传递给 UserService 的构造函数,并将其保存在 UserService 结构体中。这样,在创建 UserService 对象时,需要先创建 UserRepository 对象,并将其传递给 NewUserService 函数
属性注入
属性注入是将依赖对象作为对象属性进行注入的方式
type UserService struct {
userRepository *UserRepository
}
func (u *UserService) SetUserRepository(userRepository *UserRepository) {
u.userRepository = userRepository
}
UserService 依赖于 UserRepository。通过 SetUserRepository 方法将 UserRepository 注入到 UserService 中
方法注入
方法注入是将依赖对象作为方法参数进行注入的方式
type UserService struct {}
func (u *UserService) SaveUser(user *User, userRepository *UserRepository) error {
return userRepository.SaveUser(user)
}
UserService 依赖于 UserRepository。通过 SaveUser 方法将 UserRepository 注入到 UserService 中
全局变量
如果你想更简单些,且依赖的资源是线程安全的,也可直接定义个全局变量来存储类,这样整个项目直接调用该变量即可 更加的简单。特别适用于一些非脚手架框架,定制化比较高的轻量级框架 如Gin等
手动依赖注入的一些好处包括:
- 可以更轻松地追源码,因为依赖项显式传递给函数或构造函数,例如go-zero框架的svc
- 管理依赖项更简单
- 测试依赖于手动依赖项注入的代码可能更容易,尤其是在依赖项彼此松散耦合的情况下。
三方依赖注入库
手动管理依赖其实挺好的,对一些有代码自动化洁癖、类多到爆炸的场景,可能有些不适用,Google的Wire,Facebook的Inject和Uber的Dig 都是些star数比较高的库
kratos中的wire应用
自我描述:wire是一个golang写的库,通过执行
wire
命令生成xx_gen.go
文件帮你调用类的依赖
安装wire
一条命令就可以了
go get github.com/google/wire/cmd/wire
wire中有几个概念和常用方法
provider
各目录下包中类注册
injector
wire_gen.go
文件中的注视,//+build wireinject
注释确保了这个文件在我们正常编译的时候不会被引用,而 wire . 生成的文件 wire_gen.go 会包含 //+build !wireinject 注释,正常编译的时候,不指定 tag 的情况下会引用这个文件
wire.Build()
声明要获取需要调用到哪些provider函数
将通常会一起使用的依赖组合起来
wire.NewSet()
我们通过新增一个单独模块api
接口来演示依赖注入
kratos框架目录结构,几个比较重要的目录
- api 定义接口服务的地方
- cmd 项目的main方法入口文件,一切的源头
- configs 静态配置文件
- internal 项目的核心文件,相当于大多数时间编码的地方
4.1 biz 逻辑层,logic层
4.2 conf 配置文件读取
4.3 server http和grpc实例的创建和配置
4.4 service api目录中定义的接口服务handle层
第一步:api目录中定义服务
api/workbench/v1/workbench.proto
rpc ProjectCommonList(ListPortfolioRequest) returns (ListPortfolioReply){
option (google.api.http) = {
post: "/workbench/v1/commonproject/list"
body: "*"
};
}
第二步:生成对应的pb类
命令行执行
make api
第三步:编写对应的接口handle方法
internal/service/workbench.go
CommercialService
是新增的的logic
type WorkbenchService struct {
v1.UnimplementedWorkbenchServer
log *log.Helper
userService *biz.UserService
fileService *biz.FileService
projectService *biz.ProjectService
commonProjectService *biz.CommonProjectService
reviewService *biz.ReviewService
teamService *biz.TeamService
spaceService *biz.SpaceService
portfolioService *biz.PortfolioService
permService *biz.PermService
commercialService *biz.CommercialService
}
func NewWorkbenchService(logger log.Logger, userService *biz.UserService, fileService *biz.FileService, projectService *biz.ProjectService,
review *biz.ReviewService, team *biz.TeamService, space *biz.SpaceService, portfolio *biz.PortfolioService,
perm *biz.PermService, commercial *biz.CommercialService, commomnproject *biz.CommonProjectService) *WorkbenchService {
return &WorkbenchService{
log: log.NewHelper(logger),
userService: userService,
fileService: fileService,
projectService: projectService,
reviewService: review,
teamService: team,
spaceService: space,
portfolioService: portfolio,
permService: perm,
commercialService: commercial,
commonProjectService: commomnproject,
}
}
func (w *WorkbenchService) ProjectCommonList(ctx context.Context, request *v1.ListPortfolioRequest) (*v1.ListPortfolioReply, error) {
ret := &v1.ListPortfolioReply{
Action: request.Action,
Model: request.Model,
RequestId: request.RequestId,
UserIds: make([]uint32, 0),
}
data, userIds, err := w.commonProjectService.ProjectCommonList(ctx, request)
if err != nil {
if se := new(kraErrors.Error); kraErrors.As(err, &se) {
ret.Code = se.Code
ret.Message = se.Message
return ret, nil
}
w.log.Errorf("SetDefaultTab biz failed,err=%v", err)
return nil, err
}
ret.Data = data
ret.UserIds = userIds
return ret, nil
}
第四步:编写对应的接口logic方法
biz文件夹中,新建commonproject.go
文件
internal/biz/commonproject.go
package biz
import (
"context"
v1 "workbench/api/workbench/v1"
"github.com/go-kratos/kratos/v2/log"
)
// CommonProjectService 项目相关接口
type CommonProjectService struct {
log *log.Helper
}
func NewCommonProjectService(logger log.Logger) *CommonProjectService {
return &CommonProjectService{
log: log.NewHelper(logger),
}
}
func (s *CommonProjectService) ProjectCommonList(ctx context.Context, request *v1.ListPortfolioRequest) (*v1.ListPortfolioReply_Data, []uint32, error) {
return nil, nil, nil
}
第五步:将刚才新增的CommonProjectService.go
类,注册到wire
体系中
krato框架的每个目录下,都有个与文件夹同名的xxx.go
文件,里面用来做wire
的注册功能
internal/biz/biz.go
package biz
import (
"workbench/internal/biz/conf"
"github.com/google/wire"
)
// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewFileService, NewProjectService, NewReviewService, NewUserService, NewTeamService, NewSpaceService, NewPermService, NewPortfolioService, NewCommercialService, conf.NewTabService, NewBroadcastScopeService, NewCommonProjectService)
第六步:生成wire
依赖注入
cmd/workbench/ 入口目录下执行命令wire
wire
第七步:运行项目
make build
./bin/workbench
curl 0.0.0.0:8001/workbench/v1/commonproject/list
步骤回顾
kratos框架中,习惯性的将每个目录下新建一个与文件夹同名的xx.go
文件,用来注册该目录下的类,例如biz
目录中的biz.go
中通过wire.NewSet()
注册该目录下的所有类
main.go
为主注册入口,该文件夹中有个wire.go
文件,通过wire.Build
方法加载了各个目录下的所有依赖注册文件,通过wire命令生成了一个wire_gen.go
文件[此文件不可编辑,由wire自身维护]
wire.go
func wireApp(*conf.Server, *conf.Data, *conf.App, *conf.Registry, *conf.Auth, *conf.Plan, log.Logger) (*kratos.App, func(), error) {
panic(wire.Build(server.ProviderSet, service.ProviderSet, data.ProviderSet, newApp, biz.ProviderSet))
}
main.go
在主文件中,通过调用wire_gen.go
中的wireApp
方法,即可实现所有的类的加载使用
app, cleanup, err := wireApp(bc.Server, bc.Data, bc.App, bc.Registry, bc.Auth, &plan, logger)
整体下来的注册流程为
- 每个目录下的同名文件
目录名.go
中通过wire.NewSet
方法注册该目录下所有类,赋值给该目录下的ProviderSet
变量 - 主文件目录下的
wire.go
文件,通过wire.Build
方法加载各目录下注册的ProviderSet
- 通过
wire
命令生成wire_gen.go
文件,主函数调用wire_gen.go
文件中的wireApp
即可实现类的全部依赖注入