注:本文是基于Windos系统上Go SDK v1.8、 github.com/uber-go/dig@v1.14.1进行讲解;
1.依赖输注入介绍2.main函数反面例子本文介绍在golang中如何通过依赖注入(Dependency Inject,简称DI)管理全局服务。
DI是把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入。
直接看,你会发现main()包含清晰的初始化流程。
但是仔细想想,随着业务的扩展,我们如果把基础服务的所有实例都在main函数里生成,即 config、db、person、Repository、server等,main函数将变得越来越臃肿。
而且如果有其他方法使用上面的实例时,需要通过参数的方式传递给方法所在包,例如:service.NewPersonService(config, personRepository),需要将config、personRepository传递给service包
func main() {
// 生成config实例
config := NewConfig()
// 连接数据库
db, err := ConnectDatabase(config)
// 判断是否有错误
if err != nil {
panic(err)
}
// 生成repository实例,用于获取person数据,参数是db
personRepository := repo.NewPersonRepository(db)
// 生成service实例,用于调用repository的方法
personService := service.NewPersonService(config, personRepository)
// 生成http服务实例
server := NewServer(config, personService)
// 启动http服务
server.Run()
}
// main包下的函数
func NewServer(config *config.Config, service *service.PersonService) *Server {
return &Server{
config: config,
personService: service,
}
}
// main包下的函数
func NewConfig() *Config {
// ...
}
// main包下的函数
func ConnectDatabase(config *config.Config) (*sql.DB, error) {
// ...
}
//repo包下的函数
func NewPersonRepository(database *sql.DB) *PersonRepository {
// ...
}
//service包下的函数
func NewPersonService(config *config.Config, repository *repo.PersonRepository) *PersonService {
// ...
}
// Server
type Server struct {
config *config.Config
personService *service.PersonService
}
// Server下的方法,ServerHandler
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/people", s.people)
return mux
}
//Server下的方法, Run
func (s *Server) Run() {
httpServer := &http.Server{
Addr: ":" + s.config.Port,
Handler: s.Handler(),
}
httpServer.ListenAndServe()
}
// Server下的方法,people
func (s *Server) people(w http.ResponseWriter, r *http.Request) {
people := s.personService.FindAll()
bytes, _ := json.Marshal(people)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(bytes)
}
3.下载DI依赖
我使用的是uber的dig包
go get github.com/uber-go/dig
4.main函数使用DI优化
这样的main函数不需要包含任何基础实例的初始化和参数传递的过程,可以称之:Perfect!
下面是对main函数里基础服务注入的流程说明:
1)BuildContainer,只将各个基础服务的实例化方法注入到容器里,还没有调用这些方法来实例化基础服务
2)container.Invoke,这里将会从容器里寻找server实例,来运行server.Run()。如果实例不存在,则调用其实例化的方法,NewServer
3)因为
NewServer(config *config.Config, service *service.PersonService) *Server
依赖于config.Config和service.PersonService,
故触发NewConfig、NewPersonService
4)NewConfig不依赖于任何实例,故可以成功返回config.Config实例
5)NewPersonService(config *config.Config, repository *repo.PersonRepository) *PersonService
依赖
config.Config和repo.PersonRepository
,继而触发repo.NewPersonRepository去实例化repo.PersonRepository
6)repo.NewPersonRepository方法依赖于db,
故触发ConnectDatabase方法,用来连接数据库,实例化db实例
7)最后递归倒推回去,完成所有实例的初始化与注入,调用server.Run()方法启动http服务。
注意,有依赖的初始化方法,需要放在前置依赖注入之后,比如container.Provide(ConnectDatabase)就放在container.Provide(NewConfig)之后。如果找不到初始化需要的依赖对象,在Invoke时就会报错。
// 构建一个DI容器
func BuildContainer() *dig.Container {
container := dig.New()
// 注入config的实例化方法
container.Provide(NewConfig)
// 注入database的实例化方法
container.Provide(ConnectDatabase)
// 注入repository的实例化方法
container.Provide(repo.NewPersonRepository)
// 注入service的实例化方法
container.Provide(service.NewPersonService)
// 注入server
container.Provide(NewServer)
return container
}
func main() {
container := BuildContainer()
err := container.Invoke(func(server *Server) {
server.Run()
})
if err != nil {
panic(err)
}
}
5.注意点
之前我通过下面的方式去获取容器里的基础实例:
package app
// Config 配置文件
func Config() (conf *config.Config) {
_ = container.Invoke(func(c *config.Config) {
conf = c
})
return
}
// 其他package
fmt.Println(app.Config().GetString("someKey"))
这样去获取基础实例是不正确的用法,因为底层是通过一个map来管理这些实例的,我们都知道不是线程安全的,在频繁调用时会出现以下错误:
注:concurret是"同时发生的意思"
所以,我们在使用注入的时候,将如这样依赖和的实例函数,在main函数里通过注入进去,这样仅调用一次,保证线程安全。d
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)