Golang 实现依赖注入
什么是依赖注入
依赖注入就是将实例变量传入到一个对象中去
为何要做依赖注入
让开发者从对项目中大量依赖的创建和管理中解脱出来
控制反转(IoC)与依赖注入(DI)
控制反转(Inversion of Control)是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。
控制反转和依赖注入的关系
我们已经分别解释了控制反转和依赖注入的概念。有些人会把控制反转和依赖注入等同,但实际上它们有着本质上的不同。
- 控制反转是一种思想
- 依赖注入是一种设计模式
- 依赖注入可以作为实现控制反转的方式
测试程序的依赖关系
我们用来测试的程序的依赖关系
没有依赖注入之前
package main
import (
"fmt"
"log"
"net/http"
)
type Config struct{
}
type DB struct {
config *Config
}
type PersonRepositor struct {
db *DB
}
type PersonService struct {
personRepositor *PersonRepositor
}
type Server struct {
personService *PersonService
}
func (s *Server) Run() {
fmt.Print("Server Run\n")
HTTPServer()
}
func NewConfig() *Config {
fmt.Print("new Config\n")
return new(Config)
}
func ConnectDatabase(c *Config) *DB {
fmt.Printf("new DB, need config:%+v\n", c)
return new(DB)
}
func NewPersonRepository(db *DB) *PersonRepositor {
fmt.Printf("new PersonRepositor, need DB:%+v\n", db)
return new(PersonRepositor)
}
func NewPersonService(personRepositor *PersonRepositor) *PersonService {
fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personRepositor)
return new(PersonService)
}
func NewServer(personService *PersonService) *Server {
fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personService)
return new(Server)
}
func main() {
config := NewConfig()
db := ConnectDatabase(config)
personRepository := NewPersonRepository(db)
personService := NewPersonService(personRepository)
server := NewServer(personService)
server.Run()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello there!\n")
}
func HTTPServer() {
http.HandleFunc("/", myHandler) // 设置访问路由
log.Fatal(http.ListenAndServe(":8080", nil))
}
-
我们首先创建
Config
,然后使用Config
创建数据库连接。接下来我们可以创建PersonRepository
,这样就可以创建PersonService
了,最后,我们使用这些来创建Server
并启动它。现在这个简单的程序看起来还是不是复杂,但是当我们的程序依赖复杂起来的时候,整个手动依赖注入的就会显得非常多,而且复杂。想象一下,如果项目中有上百个依赖,那么就会有上百行的New...(...)
代码。 -
这个时候,你就会想,是不是有框架自动做这些依赖的注入。事实上,确实有人做了这样的事情,比如
uber.org/dig、facebookgo/inject 、google/wire
。接下来,我们将简单看下如何使用这些框架来帮助我们实现自动的依赖注入
Golang 实现依赖注入的几种方式
uber.org/dig、facebookgo/inject 、google/wire
使用反射
uber.org/dig 对 Go 项目进行依赖注入
使用方式
package main
import (
"fmt"
"log"
"net/http"
"go.uber.org/dig"
)
type Config struct{
Name string `json:"name"`
}
type DB struct {
Config *Config
}
type PersonRepositor struct {
DB *DB
}
func (p PersonRepositor) Hello(){
fmt.Printf("hello, test for:%s\n", p.DB.Config.Name)
}
type PersonService struct {
PersonRepositor *PersonRepositor
}
type Server struct {
PersonService *PersonService
}
func (s *Server) Run(){
fmt.Print("Server Run\n")
s.PersonService.PersonRepositor.Hello()
HTTPServer()
}
func NewConfig() *Config{
fmt.Print("new Config\n")
c := new(Config)
c.Name = "go.uber.org/dig"
return c
}
func ConnectDatabase(c *Config) *DB {
fmt.Printf("new DB, need config:%+v\n", c)
db := new(DB)
db.Config = c
return db
}
func NewPersonRepository(db *DB) *PersonRepositor {
fmt.Printf("new PersonRepositor, need DB:%+v\n", db)
p := new(PersonRepositor)
p.DB = db
return p
}
func NewPersonService(personRepositor *PersonRepositor) *PersonService {
fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personRepositor)
p := new(PersonService)
p.PersonRepositor = personRepositor
return p
}
func NewServer(personService *PersonService) *Server {
fmt.Printf("new PersonService, need PersonRepositor:%+v\n", personService)
s := new(Server)
s.PersonService = personService
return s
}
func BuildContainer() *dig.Container {
container := dig.New()
container.Provide(NewConfig)
container.Provide(ConnectDatabase)
container.Provide(NewPersonRepository)
container.Provide(NewPersonService)
container.Provide(NewServer)
return container
}
func main() {
container := BuildContainer()
err := container.Invoke(func(server *Server) {
server.Run()
HTTPServer()
})
if err != nil {
panic(err)
}
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello there!\n")
}
func HTTPServer() {
http.HandleFunc("/", myHandler) // 设置访问路由
log.Fatal(http.ListenAndServe(":8080", nil))
}
如何使用
从上面的代码我们可以看到,用dig 来实现依赖注入。
- 我们只需要将我们的被依赖方的创建方式注册到Provide中
- 我们的服务在启动的时候,只需要在Invoke中启动就可以了
- 更多使用方式:
https://pkg.go.dev/go.uber.org/dig#section-documentation
facebookgo/inject 对 Go 项目进行依赖注入
使用方式
package main
import (
"fmt"
"log"
"net/http"
"github.com/facebookarchive/inject"
)
type Config struct{
Name string `json:"name"`
}
type DB struct {
Config *Config `inject:""`
}
type PersonRepositor struct {
DB *DB `inject:""`
}
func (p PersonRepositor) Hello(){
fmt.Printf("hello, test for:%s\n", p.DB.Config.Name)
}
type PersonService struct {
PersonRepositor *PersonRepositor `inject:""`
}
type Server struct {
PersonService *PersonService `inject:""`
}
func (s *Server) Run(){
fmt.Print("Server Run\n")
s.PersonService.PersonRepositor.Hello()
HTTPServer()
}
func NewConfig() *Config{
fmt.Print("new Config\n")
c := new(Config)
c.Name = "facebookgo-inject"
return c
}
func main() {
graph := inject.Graph{}
conf := NewConfig()
server := Server{}
if err := graph.Provide(
&inject.Object{
Value: &server,
},
&inject.Object{
Value: conf,
},
); err != nil {
panic(err)
}
if err := graph.Populate(); err != nil {
panic(err)
}
server.Run()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello there!\n")
}
func HTTPServer(){
http.HandleFunc("/", myHandler) // 设置访问路由
log.Fatal(http.ListenAndServe(":8080", nil))
}
如何使用
- 首先每一个需要注入的字段都需要打上 inject:“” 这样的 tag。所谓依赖注入,这里的依赖指的就是对象中包含的字段,而注入则是指有其它程序会帮你对这些字段进行赋值。
- 其次,我们使用 inject.Graph{} 创建一个 graph 对象。这个 graph 对象将负责管理和注入所有的对象。至于为什么叫 Graph,其实这个名词起的非常形象,因为各个对象之间的依赖关系,也确实像是一张图一样。
- 接下来,我们使用 graph.Provide() 将需要注入的对象提供给 graph。
- 最后调用 Populate 函数,开始进行注入。
文档参考