接触 Golang 有一段时间了,发现 Golang 同样需要类似 Java 中 Spring 一样的依赖注入框架。如果项目规模比较小,是否有依赖注入框架问题不大,但当项目变大之后,有一个合适的依赖注入框架是十分必要的。通过调研,了解到 Golang 中常用的依赖注入工具主要有 Inject 、Dig 等。但是今天主要介绍的是 Go 团队开发的 Wire,一个编译期实现依赖注入的工具。
说起就要引出另一个名词( IoC )。IoC 是一种设计思想,其核心作用是降低代码的耦合度。是一种实现且用于解决依赖性问题的设计模式。
在go中,依赖注入是指组件创建的时候,就应该获取该组件所依赖的其他关系,如下代码,要创建一个App示例则需要Config和service结构体。
go
type App struct {
conf *Config
httpServer *Server
}
// 构建 Config
func InitConfig() *Config {
return &Config{}
}
// 构建 Server
func NewHttpServer() *Server {
return &Server{}
}
// 构建 App (内部调用依赖组件的构建函数)
func NewApp() *App {
return &App{
conf: InitConfig(),
httpServer: NewHttpServer(),
}
}
// 构建 App (需由调用者传入依赖组件)
func NewAppByDI(conf *Config, httpSrv *Server) *App {
return &App{
conf: conf,
httpServer: httpSrv,
}
}
func main() {
// 未使用依赖注入
app := NewApp()
// 使用依赖注入
config := InitConfig()
server := NewHttpServer()
app := NewAppByDI(config, server)
}
未使用依赖注入的情况下,调用者无法知道 内部使用了 和 ,如果 构建函数 发生变化,假设需要增加一个参数,我们就需要在每个调用 的地方修改代码。
使用依赖注入的情况下,将 和 的构建逻辑与构建 的逻辑分离开,即使 的构建函数 发生了变化,也只需要修改一处代码,但是在构建 之前,需要手动构建 和 ,随着程序推进,这些依赖关系将越来越复杂,这时候就需要依赖注入工具 来帮助我们生成依赖关系。
如果上面的代码不够清晰,再看一个例子
javascript
// NewUserRepositoryV1非依赖注入的写法
func NewUserRepositoryV1(dbCfg DBConfig, c CacheConfig)*UserRepository{
db, err := gorm.Open(mysql.Open(dbcfg.DSN))
if err != nil {
panic(err)
}
ud = dao.NewUserDAO(db)
uc = cache.NewUserCache(redis.NewClient(&redis.Options{
Addr: c.Addr,
}))
return &UserRepository{
dao: ud,
cache: uc,
}
}
// NewUserRepository 依赖注入的写法
func NewUserRepository(d *dao.UserDAO, c *cache.UserCache)*UserRepository{
return &UserRepository{
dao: d,
cache: c,
}
}
shell
# 导入项目
go get -u github.com/google/wire
# 安装命令
go install github.com/google/wire/cmd/wire
plain text
wire
├── db.go # 数据库相关代码
├── go.mod # Go模块依赖配置文件
├── go.sum # Go模块依赖校验文件
├── main.go # 程序入口文件
├── repository # 存放数据访问层代码的目录
│ ├── dao # 数据访问对象(DAO)目录
│ │ └── user.go # 用户相关的DAO实现
│ └── user.go # 用户仓库实现
├── wire.go # Wire依赖注入配置文件
go
// repository/dao/user.gopackage dao
import "gorm.io/gorm"
type UserDAO struct {
db *gorm.DB
}
func NewUserDAO(db *gorm.DB) *UserDAO {
return &UserDAO{
db: db,
}
}
go
// repository/user.gopackage repository
import "wire/repository/dao"
type UserRepository struct {
dao *dao.UserDAO
}
func NewUserRepository(dao *dao.UserDAO) *UserRepository {
return &UserRepository{
dao: dao,
}
}
go
// db.gopackage wire
import ("gorm.io/driver/mysql""gorm.io/gorm")
func InitDB() *gorm.DB {
db, err := gorm.Open(mysql.Open("dsn"))
if err != nil {
panic(err)
}
return db
}
go
package wire
import ("fmt""gorm.io/driver/mysql""gorm.io/gorm""wire/repository""wire/repository/dao")func main() {// 非依赖注入
db, err := gorm.Open(mysql.Open("dsn"))if err != nil {panic(err)}
ud := dao.NewUserDAO(db)
repo := repository.NewUserRepository(ud)
fmt.Println(repo)}
shell
# 导入项目
go get -u github.com/google/wire
# 安装命令
go install github.com/google/wire/cmd/wire
javascript
//go:build wireinject
// 上面这个注释是一个构建约束,它告诉 go build 只有在满足条件 wireinject 的情况下才应该构建这个文件。wireinject 是一个特殊的标签,用于指示 wire 工具处理这个文件。
package wire
import (
"github.com/google/wire"
"wire/repository"
"wire/repository/dao"
)
func InitRepository() *repository.UserRepository {
wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
return new(repository.UserRepository)
}
go
type Fooer interface {
Foo() string
}
type MyFooer string
func (b *MyFooer) Foo() string {
return string(*b)
}
func provideMyFooer() *MyFooer {
b := new(MyFooer)
*b = "Hello, World!"
return b
}
type Bar string
func provideBar(f Fooer) string {
// f will be a *MyFooer.
return f.Foo()
}
var Set = wire.NewSet(
provideMyFooer,
wire.Bind(new(Fooer), new(*MyFooer)),
provideBar,
)
go
type Foo int
type Bar int
func ProvideFoo() Foo {/* ... */}
func ProvideBar() Bar {/* ... */}
type FooBar struct {
MyFoo Foo
MyBar Bar
}
var Set = wire.NewSet(
ProvideFoo,
ProvideBar,
wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)