一个Go编程语言的运行依赖注入库。依赖注入是更广泛的控制反转技术的一种形式。它用于增加程序的模块化并使其具有可扩展性。
type A struct { Name string } func NewA() *A { r := rand.New(rand.NewSource(time.Now().UnixNano())) name := "A-" + strconv.Itoa(r.Int()) return &A{Name: ls} } services := NewServiceCollection() services.AddSingleton(NewA) //serviceCollection.AddSingletonByImplementsAndName("redis-master", NewRedis, new(abstractions.IDataSource)) //serviceCollection.AddTransientByImplements(NewRedisClient, new(redis.IClient)) //serviceCollection.AddTransientByImplements(NewRedisHealthIndicator, new(health.Indicator)) serviceProvider := services.Build() var env *A _ = serviceProvider.GetService(&env) // used
依赖注入是更广泛的控制反转技术的一种形式。它用于增加程序的模块化并使其具有可扩展性。
go get -u github.com/yoyofxteam/dependencyinjection@v1.0.1
首先,我们需要创建两个基本类型:http。服务器和http.ServeMux。让我们创建一个简单的构造函数来初始化它:
// NewServer creates a http server with provided mux as handler. func NewServer(mux *http.ServeMux) *http.Server { return &http.Server{ Handler: mux, } } // NewServeMux creates a new http serve mux. func NewServeMux() *http.ServeMux { return &http.ServeMux{} }
支持的构造器签名形式如下:
func([dep1, dep2, depN]) (result, [cleanup, error])
现在让我们来容器构建这些类型。
下面展示是Low API, 也可以用High API来构建:
import ( di "github.com/yoyofxteam/dependencyinjection" ) container := di.New( // provide http server di.Provide(NewServer), // provide http serve mux di.Provide(NewServeMux) )
函数di. new()解析构造函数,编译依赖关系图并返回*di。用于交互的容器类型。如果无法编译,容器会出现恐慌。
我认为在应用程序初始化时而不是在运行时出现恐慌是很常见的。
我们可以从容器中提取构建的服务器。为此,定义提取类型的变量,并将变量指针传递给Extract函数。
如果未找到提取的类型或构建实例的过程导致错误,则提取返回错误。
如果没有错误发生,我们就可以使用这个变量,就像我们自己构建它一样。
// declare type variable var server *http.Server // extracting err := container.Extract(&server) if err != nil { // check extraction error } server.ListenAndServe()
请注意,默认情况下,容器作为单例创建实例 , 但也可使用Provide做行为上的改变。
## Invocation 作为提取的替代方法,我们可以使用Invoke()函数。它解析函数依赖并调用函数。调用函数可能返回可选错误。 ```golang // NewServer creates a http server with provided mux as handler. func NewServer(handler http.Handler) *http.Server { return &http.Server{ Handler: handler, } }
对于一个容器来说,要知道作为http的实现。处理程序是必须使用的,我们使用选项di.As()。这个选项的参数必须是一个指向接口的指针,比如new(Endpoint)。
这种语法可能看起来很奇怪,但我还没有找到更好的方式来指定接口。
修改依赖注入容器初始化代码:
container := inject.New( // provide http server inject.Provide(NewServer), // provide http serve mux as http.Handler interface inject.Provide(NewServeMux, inject.As(new(http.Handler))) )
现在容器使用提供*http。ServeMux作为http。服务器构造函数中的处理程序。使用接口有助于编写更多可测试的代码。
容器自动将接口的所有实现分组到[]
让我们使用这个特性添加一些http控制器。控制器有典型的行为。它正在注册路由。首先,将为它创建一个接口。
// Controller is an interface that can register its routes. type Controller interface { RegisterRoutes(mux *http.ServeMux) }
现在我们将编写控制器并实现控制器接口。
// OrderController is a http controller for orders. type OrderController struct {} // NewOrderController creates a auth http controller. func NewOrderController() *OrderController { return &OrderController{} } // RegisterRoutes is a Controller interface implementation. func (a *OrderController) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/orders", a.RetrieveOrders) } // Retrieve loads orders and writes it to the writer. func (a *OrderController) RetrieveOrders(writer http.ResponseWriter, request *http.Request) { // implementation }
UserController
// UserController is a http endpoint for a user. type UserController struct {} // NewUserController creates a user http endpoint. func NewUserController() *UserController { return &UserController{} } // RegisterRoutes is a Controller interface implementation. func (e *UserController) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/users", e.RetrieveUsers) } // Retrieve loads users and writes it using the writer. func (e *UserController) RetrieveUsers(writer http.ResponseWriter, request *http.Request) { // implementation }
就像在这个接口的例子中,我们将使用inject.As()提供provide选项。
container := inject.New( di.Provide(NewServer), // provide http server di.Provide(NewServeMux), // provide http serve mux // endpoints di.Provide(NewOrderController, di.As(new(Controller))), // provide order controller di.Provide(NewUserController, di.As(new(Controller))), // provide user controller )
现在,我们可以在mux中使用控制器数组。参见更新后的代码:
// NewServeMux creates a new http serve mux. func NewServeMux(controllers []Controller) *http.ServeMux { mux := &http.ServeMux{} for _, controller := range controllers { controller.RegisterRoutes(mux) } return mux }
在某些情况下,一种类型有多个实例。例如两个数据库实例:master -用于写,slave -用于读。
// MasterDatabase provide write database access. type MasterDatabase struct { *Database } // SlaveDatabase provide read database access. type SlaveDatabase struct { *Database }
第二种方法是使用带有di.WithName()提供选项的命名定义
// provide master database di.Provide(NewMasterDatabase, di.WithName("master")) // provide slave database di.Provide(NewSlaveDatabase, di.WithName("slave"))
如果需要从容器中提取,请使用di.Name()提取选项。
var db *Database container.Extract(&db, di.Name("master"))
如果需要在其他构造函数中提供命名定义,请使用di。参数嵌入。
// ServiceParameters type ServiceParameters struct { di.Parameter // use `di` tag for the container to know that field need to be injected. MasterDatabase *Database `di:"master"` SlaveDatabase *Database `di:"slave"` } // NewService creates new service with provided parameters. func NewService(parameters ServiceParameters) *Service { return &Service{ MasterDatabase: parameters.MasterDatabase, SlaveDatabase: parameters.SlaveDatabase, } }
// ServiceParameter type ServiceParameter struct { di.Parameter StdOutLogger *Logger `di:"stdout"` FileLogger *Logger `di:"file,optional"` }
将依赖声明为可选的构造函数必须处理这些依赖不存在的情况。
如果需要在定义级别上指定一些参数,则可以使用 inject.ParameterBag provide option. 这是一个map[string]interface{},可以转换为di.ParameterBag类型。
// Provide server with parameter bag di.Provide(NewServer, di.ParameterBag{ "addr": ":8080", }) // NewServer create a server with provided parameter bag. Note: use di.ParameterBag type. // Not inject.ParameterBag. func NewServer(pb di.ParameterBag) *http.Server { return &http.Server{ Addr: pb.RequireString("addr"), } }
如果您想在每次提取上创建一个新实例,请使用di.Prototype()提供选项。
di.Provide(NewRequestContext, di.Prototype())
如果提供程序创建了一个需要清理的值,那么它可以返回一个闭包来清理资源。
func NewFile(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 }
在调用container.Cleanup()之后,它遍历实例,如果存在则调用cleanup函数。
container := di.New( // ... di.Provide(NewFile), ) // do something container.Cleanup() // file was closed
Dependency injection 是一个运行时的依赖注入框架, 它也会集成到了微服务框架 yoyogo 中. 🦄🌈 YoyoGo (Go语言框架)一个简单、轻量、快速、基于依赖注入的微服务框架( web 、grpc ),支持Nacos/Consoul/Etcd/Eureka/k8s /Apollo等 .
参考:
yoyogo: https://github.com/yoyofx/yoyogo
dependencyinjection: https://github.com/yoyofxteam/dependencyinjection