依赖注入器

February 15, 2014

前些日子,在读Martini源码的时候,第一次接触到依赖注入的概念,当时我基本领会了它的思想,但Martini作者实现的一个注射器,我却没看懂。今天,我终于搞明白了,现在试着把它解释清楚。

注:如果你还不知道什么是依赖注入,请先去看下我的这篇文章:依赖注入 (Dependency Injection,DI)

使用场景

拿Martini框架的来说,每一个web请求进来之后,都有一个特定的handler来处理它,如下:

func Price(r *render.Render, db *sql.DB, w http.ResponseWriter, req *http.Request) {
} 

我们把它传到router的时候如下:

router.Get("/price", web.Price)

那么 r, db, w, req 这些参数从哪里来呢,最直接的,当然从调用它的对象那里来。那么显然,router会提供这些参数的实例。而runter又是由martini实例化的,参数最终掌握在martini手中。

router := martini.NewRouter()

从我们的分析可知,martini必须要管理这些参数的实例化工作。当然,它不会自己管理,这时候依赖注入器老兄就要闪亮登场了。

依赖注入器

依赖注入器想要完成上述工作,即为http handler提供参数,它必须知道以下两点:

  1. handler有哪些参数
  2. 如何给出这些参数的实例。

第1 2条都得go语言的反射机制提供帮助,而第2条则是依赖注入器的本质工作,反射只能告诉依赖注入器函数有哪些参数,分别是什么类型,但如何实例化,则是由依赖注入器负责。

所以,一个依赖注入器一般有以下功能:

  1. 存储和管理(类型-值)映射
  2. 将依赖注入到函数的参数中,即调用函数。

inject代码解析

Martini的作者写了一个非常优雅的依赖注入器,源代码在
https://github.com/codegangsta/inject

让我们来看一下它是怎么实现上述功能的:

首先是它的接口定义,我们知道,接口是对象的行为。

type Injector interface {
    Applicator
    Invoker
    TypeMapper
    SetParent(Injector)
}

type Applicator interface {
    Apply(interface{}) error
}

type Invoker interface {
    Invoke(interface{}) ([]reflect.Value, error)
}
type TypeMapper interface {
    Map(interface{}) TypeMapper
    MapTo(interface{}, interface{}) TypeMapper
    Get(reflect.Type) reflect.Value
}

看起来很复杂,其实是接口的组合,这也是go语言的灵活和优雅之处。Injector接口主要包含三个,

  • TypeMapper 管理类型和值的映射
  • Applicator 函数调用
  • Invoker 这个接口则是把injector管理的映射注入到一个struct中,额外的功能。

怎么样,注入器所需要的功能一应俱全了吧,那么(类型-值)映射存储在哪里呢,请看下面struct的定义:

type injector struct {
    values map[reflect.Type]reflect.Value
    parent Injector
}

没错,就存储在values中。parent则是在values中找不到实例化某个type的映射,上溯到父注入器那里找,都找不到再返回error。

另外,我fork一个inject的源码,加了些中文注释,也许有助于你的理解 https://github.com/rnoldo/inject

· EOF ·