Go 项目依赖注入wire工具最佳实践介绍与使用
文章目录
- 一、引入
- 二、控制反转与依赖注入
- 三、为什么需要依赖注入工具
- 3.1 示例
- 3.2 依赖注入写法与非依赖注入写法
- 四、wire 工具介绍与安装
- 4.1 wire 基本介绍
- 4.2 安装
- 五、Wire 的基本使用
- 5.1 前置代码准备
- 5.2 使用 Wire 工具生成代码
- 六、Wire 核心技术
- 5.1 抽象语法树分析
- 5.2 模板编程
- 七、Wire 的核心概念
- 7.1 两个核心概念
- 7.2 Wire 提供者(providers)
- 7.3 Wire 注入器(injectors)
- 八、Wire 的高级用法
- 8.1 绑定接口
- 8.2 结构体提供者(Struct Providers)
- 8.3 绑定值
- 8.4 使用结构体字段作为提供者(providers)
- 8.5 清理函数
- 8.6 备用注入器语法
- 九、参考文档
一、引入
在Go语言的项目开发中,为了提高代码的可测试性和可维护性,我们通常会采用依赖注入(Dependency Injection
,简称DI)的设计模式。依赖注入可以让高层模块不依赖底层模块的具体实现,而是通过抽象来互相依赖,从而使得模块之间的耦合度降低,系统的灵活性和可扩展性增强。
二、控制反转与依赖注入
控制反转(Inversion of Control
,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection
,简称DI)。依赖注入是生成灵活和松散耦合代码的标准技术,通过明确地向组件提供它们所需要的所有依赖关系。在 Go 中通常采用将依赖项作为参数传递给构造函数的形式:
构造函数NewUserRepository
在创建UserRepository
时需要从外部将依赖项db
作为参数传入,我们在UserRepository
中无需关注db
的创建逻辑,实现了代码解耦。
// NewUserRepository 创建BookRepo的构造函数
func NewUserRepository(db *gorm.DB) *UserRepository {return &UserRepository{db: db}
}
区别于控制反转,如果在NewUserRepository
函数中自行创建相关依赖,这将导致代码高度耦合并且难以维护和调试。
// NewUserRepository 创建UserRepository的构造函数
func NewUserRepository() *UserRepository {db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})return &UserRepository{db: db}
}
三、为什么需要依赖注入工具
3.1 示例
如果上面示例代码不够清晰的话,我们来看这两段代码:
// 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,}
}
可以清楚地看到,这两段代码展示了在Go语言中实现依赖注入的两种不同方式。
第一段代码 NewUserRepositoryV1
是非依赖注入的写法。在这个函数中,UserRepository
的依赖(db
和 cache
)是在函数内部创建的。这种方式的问题在于,它违反了单一职责原则,因为 NewUserRepositoryV1
不仅负责创建 UserRepository
实例,还负责创建其依赖的数据库和缓存客户端。这样做会导致代码耦合度较高,难以测试和维护。
第二段代码 NewUserRepository
是依赖注入的写法。这个函数接受 UserRepository
的依赖(*dao.UserDAO
和 *cache.UserCache
)作为参数,而不是在函数内部创建它们。这种方式使得 UserRepository
的创建与它的依赖解耦,更容易测试,因为你可以轻松地为 UserRepository
提供模拟的依赖项。此外,这种写法也更符合依赖注入的原则,因为它将控制反转给了调用者,由调用者来决定 UserRepository
实例化时使用哪些依赖项。
3.2 依赖注入写法与非依赖注入写法
依赖注入写法:不关心依赖是如何构造的。
非依赖注入写法:必须自己初始化依赖,比如说 Repository
需要知道如何初始化 DAO
和 Cache
。由此带来的缺点是:
- 深度耦合依赖的初始化过程。
- 往往需要定义额外的
Config
类型来传递依赖所需的配置信息。 - 一旦依赖增加新的配置,或者更改了初始化过程,都要跟着修改。
- 缺乏扩展性。
- 测试不友好。
- 难以复用公共组件,例如 DB 或 Redis 之类的客户端。
四、wire 工具介绍与安装
4.1 wire 基本介绍
-
Wire 是一个的 Google 开源专为依赖注入(
Dependency Injection
)设计的代码生成工具,通过自动生成代码的方式在初始编译过程中完成依赖注入。它可以自动生成用于化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。 -
Wire
分成两部分,一个是在项目中使用的依赖, 一个是命令行工具。
4.2 安装
go install github.com/google/wire/cmd/wire@latest
五、Wire 的基本使用
5.1 前置代码准备
目录结构如下:
wire
├── db.go # 数据库相关代码
├── go.mod # Go模块依赖配置文件
├── go.sum # Go模块依赖校验文件
├── main.go # 程序入口文件
├── repository # 存放数据访问层代码的目录
│ ├── dao # 数据访问对象(DAO)目录
│ │ └── user.go # 用户相关的DAO实现
│ └── user.go # 用户仓库实现
├── wire.go # Wire依赖注入配置文件
repository/dao/user.go
文件:
// repository/dao/user.go
package daoimport "gorm.io/gorm"type UserDAO struct {db *gorm.DB
}func NewUserDAO(db *gorm.DB) *UserDAO {return &UserDAO{db: db,}
}
repository/user.go
文件:
// repository/user.go
package repositoryimport "wire/repository/dao"type UserRepository struct {dao *dao.UserDAO
}func NewUserRepository(dao *dao.UserDAO) *UserRepository {return &UserRepository{dao: dao,}
}
db.go
文件:
// db.go
package wireimport ("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
}
main.go
文件:
package wireimport ("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)
}
5.2 使用 Wire 工具生成代码
现在我们已经有了基本的代码结构,接下来我们将使用 wire
工具来生成依赖注入的代码。
首先,确保你已经安装了 wire
工具。如果没有安装,可以使用以下命令安装:
go get github.com/google/wire/cmd/wire
接下来,我们需要创建一个 wire
的配置文件,通常命名为 wire.go
。在这个文件中,我们将使用 wire
的语法来指定如何构建 UserRepository
实例。
wire.go
文件:
//go:build wireinject// 让 wire 来注入这里的代码
package wireimport ("github.com/google/wire""wire/repository""wire/repository/dao"
)func InitRepository() *repository.UserRepository {// 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序// 这个方法里面传入各个组件的初始化方法wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)return new(repository.UserRepository)
}
这段代码是使用 wire
工具进行依赖注入的配置文件。在这个文件中,我们定义了一个函数 InitRepository
,这个函数的目的是为了生成一个 *repository.UserRepository
的实例。但是,这个函数本身并不包含具体的实现代码,而是依赖于 wire
工具来注入依赖。
让我们逐步解释这段代码:
-
构建约束指令:
//go:build wireinject
这行注释是一个构建约束,它告诉
go build
只有在满足条件wireinject
的情况下才应该构建这个文件。wireinject
是一个特殊的标签,用于指示wire
工具处理这个文件。 -
导入包:
import ("github.com/google/wire""wire/repository""wire/repository/dao" )
这部分导入了必要的包,包括
wire
工具库,以及项目中的repository
和dao
包,这些包包含了我们需要注入的依赖。 -
InitRepository 函数:
func InitRepository() *repository.UserRepository {// 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序// 这个方法里面传入各个组件的初始化方法wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)return new(repository.UserRepository) }
这个函数是
wire
注入的目标。它声明了一个返回*repository.UserRepository
的函数,但是函数体内部没有具体的实现代码。wire.Build
函数调用是关键, 主要是连接或绑定我们之前定义的所有初始化函数。当我们运行wire
工具来生成代码时,它就会根据这些依赖关系来自动创建和注入所需的实例。,这些函数按照依赖关系被调用,以正确地构造和注入UserRepository
实例所需的依赖。InitDB
是初始化数据库连接的函数。repository.NewUserRepository
是创建UserRepository
实例的函数。dao.NewUserDAO
是创建UserDAO
实例的函数。
wire
工具会自动生成这些函数调用的代码,并确保依赖关系得到满足。
-
返回语句:
return new(repository.UserRepository)
这个返回语句是必须的,尽管它实际上并不会被执行。
wire
工具会生成一个替换这个函数体的代码,其中包括所有必要的依赖注入逻辑。
在编写完wire.go
文件后,你需要运行wire
命令来生成实际的依赖注入代码。生成的代码将被放在一个名为wire_gen.go
的文件中,这个文件应该被提交到你的版本控制系统中。
现在,我们可以运行 wire
命令来生成依赖注入的代码:
wire
这个命令会扫描 wire.go
文件,并生成一个新的 Go 文件 wire_gen.go
,其中包含了 InitializeUserRepository
函数的实现,这个函数会创建并返回一个 UserRepository
实例,其依赖项已经自动注入。
生成 wire_gen.go
文件,内容如下所示:
// Code generated by Wire. DO NOT EDIT.//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinjectpackage wireimport ("wire/repository""wire/repository/dao"
)// Injectors from wire.go:func InitRepository() *repository.UserRepository {db := InitDB()userDAO := dao.NewUserDAO(db)userRepository := repository.NewUserRepository(userDAO)return userRepository
}
最后,我们需要修改 main.go
文件,使用 wire
生成的代码来获取 UserRepository
实例:
package wirefunc main() {InitRepository()
}
现在,当我们运行 main.go
时,它将使用 wire
工具生成的代码来初始化 UserRepository
,包括其依赖的 UserDAO
和数据库连接。这样,我们就实现了依赖注入,并且代码更加简洁、易于维护。
六、Wire 核心技术
5.1 抽象语法树分析
wire
工具的工作原理是基于对Go代码的抽象语法树(Abstract Syntax Tree,简称AST)的分析。AST是源代码的抽象语法结构的树状表示,它以树的形式表现编程语言的语法结构。wire
工具通过分析AST来理解代码中的依赖关系。
在Go中,go/ast
包提供了解析Go源文件并构建AST的功能。wire
工具利用这个包来遍历和分析项目的Go代码,识别出所有的依赖项,并构建出依赖关系图。这个依赖关系图随后被用来生成注入依赖的代码。
5.2 模板编程
wire
工具生成代码的过程也涉及到模板编程。模板编程是一种编程范式,它允许开发者定义一个模板,然后使用具体的数据来填充这个模板,生成最终的代码或文本。
在wire
中,虽然不直接使用Go语言的模板引擎(如text/template
或html/template
),但它的工作原理与模板编程类似。wire
定义了一套自己的语法来描述依赖关系,然后根据这些描述生成具体的Go代码。
wire
的语法主要包括以下几个部分:
wire.NewSet
:定义一组相关的依赖,通常包括一个或多个构造函数。wire.Build
:指定生成代码时应该使用哪些依赖集合。bind
函数:用于绑定接口和实现,告诉wire
如何创建接口的实例。
wire
工具通过这些语法来构建一个依赖图,然后根据这个图生成一个函数,该函数负责创建并返回所有必要的组件实例,同时处理它们之间的依赖关系。
通过结合抽象语法树分析和模板编程,wire
工具能够提供一种声明式的依赖注入方法,让开发者能够专注于定义依赖关系,而不是手动编写依赖注入的代码。这不仅减少了重复劳动,还提高了代码的可维护性和降低了出错的可能性。
七、Wire 的核心概念
7.1 两个核心概念
在 wire
中,有两个核心概念:提供者(providers)和注入器(injectors)。
7.2 Wire 提供者(providers)
提供者 是一个普通有返回值的 Go 函数,它负责创建一个对象或者提供依赖。在 wire
的上下文中,提供者可以是任何返回一个或多个值的函数。这些返回值将成为注入器函数的参数。提供者函数通常负责初始化组件,比如数据库连接、服务实例等。并且提供者的返回值不仅限于一个,如果有需要的话,可以额外添加一个 error
的返回值。
例如,一个提供者函数可能会创建并返回一个数据库连接:
func NewDBConnection(dsn string) (*gorm.DB, error) {db, err := gorm.Open(mysql.Open(dsn))if err != nil {return nil, err}return db, nil
}
提供者函数可以分组为提供者函数集(provider set)。使用wire.NewSet
函数可以将多个提供者函数添加到一个集合中。举个例子,例如将 user
相关的 handler
和 service
进行组合:
package webvar UserSet = wire.NewSet(NewUserHandler, service.NewUserService)
使用 wire.NewSet
函数将提供者进行分组,该函数返回一个 ProviderSet
结构体。不仅如此,wire.NewSet
还能对多个 ProviderSet
进行分组 wire.NewSet(UserSet, XxxSet)
。
package demoimport (// ..."example.com/some/other/pkg"
)// ...var MegaSet = wire.NewSet(UserSet, pkg.OtherSet)
7.3 Wire 注入器(injectors)
注入器(injectors
)的作用是将所有的提供者(providers
)连接起来,要声明一个注入器函数只需要在函数体中调用wire.Build()
。这个函数的返回值也无关紧要,只要它们的类型正确即可。这些值在生成的代码中将被忽略。回顾一下我们之前的代码:
//go:build wireinject// 让 wire 来注入这里的代码
package wireimport ("github.com/google/wire""wire/repository""wire/repository/dao"
)func InitRepository() *repository.UserRepository {// 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序// 这个方法里面传入各个组件的初始化方法wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)return new(repository.UserRepository)
}
在这个例子中,InitRepository
是一个注入器,它依赖 InitDB
和 repository.NewUserRepository
这两个提供者。
与提供者一样,注入器也可以输入参数(然后将其发送给提供者),并且可以返回错误。wire.Build
的参数和wire.NewSet
一样:都是提供者集合。这些就在该注入器的代码生成期间使用的提供者集。
八、Wire 的高级用法
8.1 绑定接口
依赖项注入通常用于绑定接口的具体实现。wire
通过类型标识将输入与输出匹配,因此倾向于创建一个返回接口类型的提供者。然而,这也不是习惯写法,因为Go的最佳实践是返回具体类型。你可以在提供者集中声明接口绑定.
我们对之前的代码进行改造:
首先,我们在UserRepository
接口中定义一些方法。例如,我们可以定义一个GetUser
方法,该方法接收一个用户ID,并返回相应的用户。 在repository/user.go
文件中:
package repositoryimport ("wire/repository/dao""gorm.io/gorm"
)type UserRepository interface {GetUser(id uint) (*User, error)
}type UserRepositoryImpl struct {dao *dao.UserDAO
}func (r *UserRepositoryImpl) GetUser(id uint) (*User, error) {return r.dao.GetUser(id)
}func NewUserRepository(dao *dao.UserDAO) UserRepository {return &UserRepositoryImpl{dao: dao,}
}
然后,我们在UserDAO
中实现这个GetUser
方法。在repository/dao/user.go
文件中:
package daoimport ("gorm.io/gorm"
)type User struct {ID uint// other fields...
}type UserDAO struct {db *gorm.DB
}func (dao *UserDAO) GetUser(id uint) (*User, error) {var user Userresult := dao.db.First(&user, id)if result.Error != nil {return nil, result.Error}return &user, nil
}func NewUserDAO(db *gorm.DB) *UserDAO {return &UserDAO{db: db,}
}
最后,我们需要更新wire.go
文件中的InitRepository
函数,以返回UserRepository
接口,而不是具体的实现。 在wire.go
文件中:
//go:build wireinjectpackage wireimport ("github.com/google/wire""wire/repository""wire/repository/dao"
)func InitRepository() repository.UserRepository {wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)return &repository.UserRepositoryImpl{}
}
使用 wire.Bind
来建立接口类型和具体的实现类型之间的绑定关系,这样 Wire
工具就可以根据这个绑定关系进行类型匹配并生成代码。
wire.Bind
函数的第一个参数是指向所需接口类型值的指针,第二个实参是指向实现该接口的类型值的指针。
8.2 结构体提供者(Struct Providers)
Wire
库有一个函数是 wire.Struct
,它能根据现有的类型进行构造结构体,我们来看看下面的例子:
package mainimport "github.com/google/wire"type Name stringfunc NewName() Name {return "小米SU7"
}type PublicAccount stringfunc NewPublicAccount() PublicAccount {return "新一代车神"
}type User struct {MyName NameMyPublicAccount PublicAccount
}func InitializeUser() *User {wire.Build(NewName,NewPublicAccount,wire.Struct(new(User), "MyName", "MyPublicAccount"),)return &User{}
}
上述代码中,首先定义了自定义类型 Name
和 PublicAccount
以及结构体类型 User
,并分别提供了 Name
和 PublicAccount
的初始化函数(providers
)。然后定义一个注入器(injectors
)InitializeUser
,用于构造连接提供者并构造 *User
实例。
使用 wire.Struct
函数需要传递两个参数,第一个参数是结构体类型的指针值,另一个参数是一个可变参数,表示需要注入的结构体字段的名称集。
根据上述代码,使用 Wire
工具生成的代码如下所示:
func InitializeUser() *User {name := NewName()publicAccount := NewPublicAccount()user := &User{MyName: name,MyPublicAccount: publicAccount,}return user
}
如果我们不想返回指针类型,只需要修改 InitializeUser
函数的返回值为非指针即可。
8.3 绑定值
有时,将基本值(通常为nil)绑定到类型是有用的。你可以向提供程序集添加一个值表达式,而不是让注入器依赖于一次性函数提供者(providers
)。
func InjectUser() User {wire.Build(wire.Value(User{MyName: "小米SU7"}))return User{}
}
在上述代码中,使用 wire.Value
函数通过表达式直接指定 MyName
的值,生成的代码如下所示:
func InjectUser() User {user := _wireUserValuereturn user
}var (_wireUserValue = User{MyName: "小米SU7"}
)
需要注意的是,值表达式将被复制到生成的代码文件中。
对于接口类型,可以使用 InterfaceValue
:
func InjectPostService() service.IPostService {wire.Build(wire.InterfaceValue(new(service.IPostService), &service.PostService{}))return nil
}
8.4 使用结构体字段作为提供者(providers)
有些时候,你可以使用结构体的某个字段作为提供者,从而生成一个类似 GetXXX
的函数。
func GetUserName() Name {wire.Build(NewUser,wire.FieldsOf(new(User), "MyName"),)return ""
}
你可以使用 wire.FieldsOf
函数添加任意字段,生成的代码如下所示:
func GetUserName() Name {user := NewUser()name := user.MyNamereturn name
}func NewUser() User {return User{MyName: Name("小米SU7"), MyPublicAccount: PublicAccount("新一代车神!")}
}
8.5 清理函数
如果一个提供者创建了一个需要清理的值(例如关闭一个文件),那么它可以返回一个闭包来清理资源。注入器会用它来给调用者返回一个聚合的清理函数,或者在注入器实现中稍后调用的提供商返回错误时清理资源。
func provideFile(log Logger, path Path) (*os.File, func(), error) {f, err := os.Open(string(path))if err != nil {return nil, nil, err}cleanup := func() {if err := f.Close(); err != nil {log.Log(err)}}return f, cleanup, nil
}
8.6 备用注入器语法
如果你不喜欢在注入器函数声明的末尾编写类似return Foo{}, nil
的语句,那么你可以简单粗暴地使用panic
:
func InitializeGin() *gin.Engine {panic(wire.Build(/* ... */))
}
九、参考文档
- 掘金依赖注入工具-wire
- 李文周的博客-依赖注入工具-wire
相关文章:

Go 项目依赖注入wire工具最佳实践介绍与使用
文章目录 一、引入二、控制反转与依赖注入三、为什么需要依赖注入工具3.1 示例3.2 依赖注入写法与非依赖注入写法 四、wire 工具介绍与安装4.1 wire 基本介绍4.2 安装 五、Wire 的基本使用5.1 前置代码准备5.2 使用 Wire 工具生成代码 六、Wire 核心技术5.1 抽象语法树分析5.2 …...

地推网推拉新致富是真的吗?靠谱平台揭秘
在互联网时代,各种平台层出不穷。为了吸引更多用户,这些平台常常会推出各种地推网推拉新活动。如果你懂得如何利用,那么你也有机会从中获得一笔不小的收入。 当然,在地推网推拉新赚钱的过程中,也需要注意一些问题。首…...

VTK使用交互器来从三维体数据中提取二维切片
VTK中鼠标消息是在交互类型对象(interactorstyle)中响应,因此通过为交互类型对象(interactorstyle)添加观察者(observer)来监听相应的消息,当消息触发时,由命令模式执行相…...

NCBI 数据下载
网上介绍的那几种直接下载NCBI数据的方法大都下载速度很慢,但是EBI (European Bioinformatics Institute) 下载很快,而且它的数据库和NCBI是共享的,所以我们可以直接从 EBI 下载。 1 、 确定要下载的 SRA 编号; 2 、 EBI (https…...
【Rust】基础语法
变量,基本类型,函数,注释和控制流,这些几乎是每种编程语言都具有的编程概念。 这些基础概念将存在于每个 Rust 程序中,及早学习它们将使你以最快的速度学习 Rust 的使用。 变量 首先必须说明,Rust 是强类…...

JVM基础:类的生命周期详解
JDK版本:jdk8 IDEA版本:IntelliJ IDEA 2022.1.3 文章目录 一. 生命周期概述二. 加载阶段(Loading)2.1 加载步骤2.2 查看内存中的对象 三. 连接阶段(Linking)3.1 连接之验证3.2 连接之准备3.3 连接阶段之解析 四. 初始化阶段(Initialization)4.1 单个类的…...

【Canvas技法】在Canvas按圆周绘制图形或是标注文字时,角度累加的方向为顺时针,起点为x轴正向
【图解说明】 【核心代码】 // 画圆弧及方向for(var i0;i<4;i){var startMath.PI/2*i;var endstartMath.PI/2;var x1180*Math.cos(start);var y1180*Math.sin(start);var x2180*Math.cos(end);var y2180*Math.sin(end);ctx.beginPath();ctx.arc(0,0,180,start,end,false);ct…...

计算机网络-TCP断开连接阶段错误应对机制
连接断开阶段 四次挥手机制:TCP连接的断开需要四次挥手,这是因为双方都需要独立地关闭数据传输。第二次和第三次挥手不能合并,因为在回复第二次挥手的时候,可能还有数据没有接收完成,所以需要先回复ACK报文,…...
springboot动态使用DruidDataSource切换数据源(动态配置多个数据源)
1、添加依赖,在pom文件中添加 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>2.5.0</version></dependency><dependency><grou…...

P8786 [蓝桥杯 2022 省 B] 李白打酒加强版
【dfs题解】---只有50分 (头一回自己用dfs做出来了dp的hard等级的大题,从来没有拿50分这么高兴过哈哈哈哈哈) #include <bits/stdc.h> using namespace std; int n,m; long long ans0; const long long mol1e97; void dfs(int h,int d,int sum) {if(h<0|…...
没有网没有移动存储的情况下两台电脑如何互相传输数据
无网无移动存储情况下两台电脑数据互传探秘 一、直连网线传输数据二、局域网文件共享其他 在信息化时代的今天,电脑作为重要的数据处理工具,在日常生活和工作中扮演着不可或缺的角色。然而,有时我们会遇到一些特殊情况,如没有网络…...

如何用putty通过ssh连接ubuntu
1. 下载和安装PuTTY 访问PuTTY官网下载PuTTY的最新版本。 2. 打开PuTTY 解压下载的文件后,找到PuTTY文件并双击打开。 3. 配置SSH连接 在ubuntu下安装ssh服务在安装ssh时,我一直遇到一个问题,原因是我的虚拟机连不上网,反复实…...
java如何实现rabbitmq的消息确认机制和消息持久化机制配置和示例
在Java中,使用RabbitMQ的客户端库(通常是AMQP客户端库,如RabbitMQ的Java客户端)可以方便地实现消息确认机制和消息持久化机制。以下是如何实现这两个机制的示例。 1、消息确认机制 RabbitMQ支持两种类型的确认:生产者…...
react 组件:Suspense
允许在子组件完成加载前展示后备方案。 children:真正的 UI 渲染内容。如果 children 在渲染中被挂起,Suspense 边界将会渲染 fallback。 fallback:真正的 UI 未渲染完成时代替其渲染的备用 UI,它可以是任何有效的 React 节点。后…...

2024-4-5修改vscode的代理
今天在vs code 上面配置go环境的时候出现了以下的报错: 2024-04-05 16:18:00.786 [info] Installing golang.org/x/tools/goplslatest FAILED 2024-04-05 16:18:00.786 [info] { “code”: 1, “killed”: false, “signal”: null, “cmd”: “E:\Go\bin\go.exe in…...
python字符切片的规则
跟range一样有三个参数,分别是x:y:z,代表的含义分别为左边界,右边界(注意该范围是左闭右开的,也就是说取不到右值)和步长。 1. 切片是从左往右还是从右往左,看的是步长的正负,如果步…...
C++ 的内存安全与效率
在C编程中,内存安全和效率是两个至关重要的考虑因素。 内存安全涉及确保程序在分配和使用内存时不会发生错误,如内存泄漏、悬挂指针、越界访问、空指针解引用等; 效率则关注如何有效地使用内存资源,减少不必要的内存分配和释放操…...

Go 实战|使用 Wails 构建轻量级的桌面应用:仿微信登录界面 Demo
概述 本文探讨 Wails 框架的使用,从搭建环境到开发,再到最终的构建打包,本项目源码 GitHub 地址:https://github.com/mazeyqian/go-run-wechat-demo 前言 Wails 是一个跨平台桌面应用开发框架,他允许开发者利用 Go …...

c++取经之路(其五)——类和对象拷贝构造函数
概念:拷贝构造函数,只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。 特征: 1. 拷贝构造函数是构造函数的一个重载形式 如: 2. 拷贝…...
YOLOv8最新改进系列:融合最新顶会提出的HCANet网络中卷积和注意力融合模块(CAFM),有效提升小目标检测性能,大幅度拉升目标检测效果!遥遥领先!
YOLOv8最新改进系列:YOLOv8最新改进系列:融合最新顶会提出的HCANet网络中卷积和注意力融合模块(CAFM),有效提升小目标检测性能,大幅度拉升目标检测效果!遥遥领先! B站全文戳这里! 详细的改进教程以及源码…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...