6 22
关于Go的依赖注入

有关依赖注入的概念,大家熟知的可能是Java系的Spring框架: 采用动态、灵活的方式来管理各种对象。 对象与对象之间的具体实现都是透明的。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。

之前使用Go开发工具的时候,常常有以下的场景

应用通过配置文件或者命令行参数获取属性数据,假如我们有多个“下游”的服务需要被调用,我们如何能动态得去做这些工作,而不是手动地掺杂很多逻辑去获取这些服务调用呢?

解决方法

可以采用 codegangsta/inject,它能在运行时注入参数,调用方法。比较出名的martini就是用它实现路由的相关功能。

演示例子


package main

import (
    "fmt"
)

type Demo struct {
}

type TestObj interface{}

func (d *Demo) Say(name string, version float64, obj TestObj) {
    fmt.Printf("Coding with %s, obj is %v, version is %f!\n", name, obj, version)
}

func main() {
    testDemo := new(Demo)
    testDemo.Say("Golang", 10.0, "语言")
}

终端输出 :

Coding with Golang, obj is 语言, version is 10.000000!

在上面的代码,是一个很常见的方法调用的例子, 基本我们大部分常见都是这种形式的,但想想,因为在go中,func是可以用于传输的,假如我们有一个channel,我们从channel 中,不断获取不同的func, 这个时候,我们手动的调用func(x,y,z)这种就比较麻烦了,因为有些方法需要两个参数, 有些方法需要三个参数, 有些方法需要更多, 这时候怎么办 ? 显然,我们需要采用一种运行时动态的手段来“美化”我们的代码。

改进后的例子:

package main

import (
    "fmt"
    "github.com/codegangsta/inject"
)

type Demo struct {
}

type TestObj interface{}

func (d *Demo) Say(name string, version float64, obj TestObj) {
    fmt.Printf("Coding with %s, obj is %v, version is %f!\n", name, obj, version)
}

func main() {
    testDemo := new(Demo)
    inj := inject.New()
    inj.Map("Golang")
    inj.MapTo("语言", (*TestObj)(nil))
    inj.Map(10.0)
    f := testDemo.Say
    inj.Invoke(f)
}

终端输出 :

Coding with Golang, obj is 语言, version is 10.000000!

通过这种方式,上游根本不需要知道下游的服务的方法细节,只需要把准备好的属性注册到injector中去就行了。

injecter中提供了几个重要的方法:

  • Map

  • MapTo

  • SetParent

MapMapTo方法都用于注入参数,保存于injector的成员values中。这两个方法的功能完全相同,唯一的区别就是Map方法用参数值本身的类型当键,而MapTo方法有一个额外的参数可以指定特定的类型当键。

为什么需要有MapTo方法?因为注入的参数是存储在一个以类型为键的map中,可想而知,当一个函数中有一个以上的参数的类型是一样时,后执行Map进行注入的参数将会覆盖前一个通过Map注入的参数。

SetParent方法用于给某个Injector指定父Injector。Get方法通过reflect.Type从injector的values成员中取出对应的值,它可能会检查是否设置了parent,直到找到或返回无效的值,最后Get方法的返回值会经过IsValid方法的校验。

如果想更深入了解注入的实战用法,可以查看martini的源码