golang 依赖控制反转(IoC)

Berkaroad · · 10208 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

  主流开发语言,为了达到项目间的低耦合,都会借助IoC框架来实现。即抽象和实现分离,使用抽象层,不用关心这些抽象层的具体实现;抽象层的实现,可以独立实现。现在比较流行的领域驱动设计(ddd),为了达到将领域层作为最核心,也需要依赖于IOC。

  回过头来,我们看看golang实现的ioc框架,有golang风格的框架,也有从其他主流语言搬过来的比较重的框架。我觉得目前实现最轻量级的,当属martini框架的ioc依赖库 github.com/codegangsta/inject  。代码行数很少,提供类型注册、接口注册、类型解析、函数注入、struct注入的方法,可以说基本的已经比较全了。从文章开头应该可以猜到,我现在一直在学习ddd,目前在.NET实际项目中边运用边学习。在实际使用中发现,ioc除了要有单例模式(Singleton)支持外,应该还有临时实例(Transient)的支持。因此萌生了我写golang下的ioc框架的原因。

  我的目的很简单,希望ioc不仅支持Singleton,还要支持Transient。最初想法是,编写一个抽象层,里面支持这两种模式的注入。其中transition部分自己独立实现,而singleton,则采用现成的 github.com/codegangsta/inject 框架,并加一层适配。Transient的实现,其特点就是,每次解析类型(Resolve)时,都需要创建一个新的对象,这个对象和先前创建的是独立的。此处我采用反射机制,根据类型创建新对象。golang中没有构造函数,为了在创建对象后并在使用前,对其初始化,我引入了构造函数的概念。这个构造函数的接口其实很简单

// Initializer is to init a struct.
type Initializer interface {
    InitFunc() interface{}
}

  在这里我吐槽下博客园,怎么插入代码,还不支持golang啊?

  这个接口很简单,就一个返回interface{}的函数。其实返回的应该是另一个函数,即为构造函数。例如:

func (container *iocContainer) InitFunc() interface{} {
    return func() {
        if !container.isInitialized {
            container.locker = &sync.RWMutex{}
            container.singleton = &singletonContainer{valuemapper: make(map[reflect.Type]reflect.Value)}
            container.transient = &transientContainer{typemapper: make(map[reflect.Type]reflect.Type)}
            container.isInitialized = true
        }
    }
}

  当初始化时,调用一次构造函数,即完成了一次初始化的操作。其实针对singleton也是一样,也需要一次初始化,只是这个初始化要求仅在第一次时进行,在这里不会因此只调用一次(因为ioc框架不知道你什么时候会被第一次调用,这里需要由构造函数的实现自己进行判断,此处可以用一个字段isInitialized进行检查是否已经初始化了)。

  都说golang的反射,性能很差,我觉得部分反射的部分功能会性能很差,但有些应该还算凑合吧。既然ioc框架实现完了,那就测试下性能。由于在调整前,性能数据没有保存,就不展示了。总之,在改版前,发现inject包,在Resolve的性能很差。经过仔细排查,发现有一处的实现很智能,即当Resolve的接口类型在已注入的类型中不存在时,会尝试将已存在的类型转为接口,如果可以转换则返回。由于golang的理念里,没有类型树。认为接口的方法都实现了,就认为实现了接口,那么判断本身就会变得耗时。也因为这个原因,我重写了singleton部分,在Resolve的时候,仅仅根据传入的类型来判断。如果这个类型在注册时为singleton,那就是singleton,且原先是接口还是类型,都原样拿出,不进行任何转换。果然发现性能有所提升。

  这是ioc容器的接口,也是最核心的:

// ReadonlyContainer is a readonly container
type ReadonlyContainer interface {
    // Resolve is to get instance of type.
    Resolve(typ reflect.Type) reflect.Value
    // Invoke is to inject to function's params, such as construction.
    Invoke(f interface{}) ([]reflect.Value, error)
}

// Container is a container for ioc.
type Container interface {
    Initializer
    ReadonlyContainer
    // Register is to register a type as singleton or transient.
    Register(val interface{}, lifecycle Lifecycle)
    // RegisterTo is to register a interface as singleton or transient.
    RegisterTo(val interface{}, ifacePtr interface{}, lifecycle Lifecycle)
    // SetParent is to resolve parent's container if current hasn't registered a type.
    SetParent(parent ReadonlyContainer)
}

  这是调用的代码:

func main() {
    var requestContext = ioc.NewContainer()
    requestContext.SetParent(iocContainer)
    requestContext.RegisterTo(&productcategoryApp.ProductCategoryApplicationServiceImpl{}, (*application.ProductCategoryApplicationService)(nil), ioc.Transient)

    commandMQAdapter := new(provider.MyCommandMQProvider)
    processor := cqrs.NewCommandProcessor(commandMQAdapter)
    processor.RegisterMiddleware((*middleware.AuthCommandMiddleware)(nil))

    // execute count
    var exeCount = 1000000
    // concurrent routine
    var concurrentCount = 1
    for true {
        var wg *sync.WaitGroup = &sync.WaitGroup{}
        time.Sleep(300 * time.Millisecond)
        startTime := time.Now().UnixNano()
        for i := 0; i < concurrentCount; i++ {
            wg.Add(1)
            go func(wg1 *sync.WaitGroup) {
                for j := 0; j < exeCount/concurrentCount; j++ {
                    requestContext.Invoke(func(productCategoryAppSvc application.ProductCategoryApplicationService, roContainer ioc.ReadonlyContainer) {
                        //processor.RegisterHandler(productCategoryAppSvc)
                    })
                }
                wg1.Done()
            }(wg)
        }
        wg.Wait()
        endTime := time.Now().UnixNano()
        consoleLog.Printf("[info] requestContext.Invoke for %d times with %d routines execute in %vms.\n", exeCount, concurrentCount, float64(endTime-startTime)/float64(time.Millisecond))
    }
}

 

  这是性能数据:

1 routine, 3 times resolve singleton and 1 times resolve transient per code invoke, invoke 1,000,000 times.

Result:

[commandprocessor] 2016/07/17 11:31:29 [info] requestContext.Invoke for 1000000 times with 1 routines execute in 4971.1971ms.
[commandprocessor] 2016/07/17 11:31:34 [info] requestContext.Invoke for 1000000 times with 1 routines execute in 4951.494214ms.
[commandprocessor] 2016/07/17 11:31:39 [info] requestContext.Invoke for 1000000 times with 1 routines execute in 4954.376794ms.

2 routine, 3 times resolve singleton and 1 times resolve transient per code invoke, invoke 1,000,000 times.

Result:

[commandprocessor] 2016/07/17 11:23:50 [info] requestContext.Invoke for 1000000 times with 2 routines execute in 2779.720723ms.
[commandprocessor] 2016/07/17 11:23:53 [info] requestContext.Invoke for 1000000 times with 2 routines execute in 2719.810844ms.
[commandprocessor] 2016/07/17 11:23:56 [info] requestContext.Invoke for 1000000 times with 2 routines execute in 2734.028326ms.

   预估下来,差不多是 2 routine, 4 resolve action, 350,000 / sec 的性能数据。我是在笔记本上进行的测试(i5双核),启用2个并发routine来测试Resolve,每次测试代码的一次执行,包含Resolve4次调用。测试下来,每秒35w次测试代码执行。这个性能,我觉得在业务系统开发中,不需要考虑性能损耗的问题了。

---------------------------------分割线-------------------------------------------------------------

我的ioc项目,已经挂在github上,有兴趣的可以去了解下。https://github.com/Berkaroad/ioc

通过go来安装ioc包:  go get github.com/berkaroad/ioc

使用中有何问题,欢迎在github上给我提issue,谢谢!

 


有疑问加站长微信联系(非本文作者)

本文来自:博客园

感谢作者:Berkaroad

查看原文:golang 依赖控制反转(IoC)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

10208 次点击  ∙  2 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传