带你学习inversify.js系列 - inversify基础知识学习(二)

发表于 2020-03-25
更新于 2024-05-23
分类于 技术专栏
阅读量 292
字数统计 5406

3、inversify的绑定过程

上一小节说的是依赖是如何从容器中取出来的,那么这一小节我们说说依赖是如何注入进去的,以及inversify提供了多少种类型绑定方法。

初学inversify的人很容易被他的绑定语法搞蒙圈,到处都是bind().toXX.whenXXX().。其实主要是你不清楚这些方法的含义,导致你很难接受这种一目了然的写法。

那么我们首先来说说这些写法的意义。

下图是inversify根据绑定后的操作不同分门别类的所有文件:

从文件命名来看,我们知道inversify提供了诸如towhenon这类的方法供我们决定一个类型绑定的诸多属性。

接下来记住这句话: 除了to语法,其余的语法其实都是在往Binding这个类实例的属性赋值

就是下面的这些属性:

所有的入口都是指向BindingToSyntax这个类,再往外衍生出各种when语法。

  • BindingToSyntax:指定class绑定到容器内的类型,从写法来说:container.bind(A).toXXX(),很好理解成绑定类A为(to)XXX。这里的XXX可以有以下几种:
    • to():必须传入一个构造器,定义的类型是:BindingTypeEnum.Instance,后续在使用的时候会new掉这个构造器
    • toSelf():to写法的一种简写方式,内部最后还是调用to
    • toConstantValue():绑定为常量,传入的是一个初始化后的实例,定义的类型是:BindingTypeEnum.ConstantValue
    • toDynamicValue():绑定为动态值,在获取的时候会去执行对应的函数,定义的类型是:BindingTypeEnum.DynamicValue
    • toConstructor():绑定为构造函数,在获取之后需要自己实例化,定义的类型是:BindingTypeEnum.Constructor
    • toFactory():绑定为工厂函数,与刚才的动态值不一样,动态值会执行完动态函数返回值,而工厂函数则会返回一个高阶函数,允许你进一步定制值,定义的类型是:BindingTypeEnum.Factory
    • toFunction():绑定为函数,其实就是toConstantValue的别名,定义的类型为:BindingTypeEnum.Function
    • toAutoFactory():绑定为自动工厂函数,此时的工厂函数不用开发者提供,内部自己实现掉了,定义的类型为:BindingTypeEnum.Factory
    • toProvider():绑定为一个异步的工厂函数,称之为Provider,对于需要一些异步操作的时候这种方式非常有用,定义的类型为:toProvider
    • toService():绑定为一个服务,让其解析为以前声明过的别的类型绑定,这个绑定很特殊,没有别的任何后续操作,因为它没有返回值,看代码就知道:
1public toService(service: string | symbol | interfaces.Newable<T> | interfaces.Abstract<T>): void { 2 this.toDynamicValue( 3 (context) => context.container.get<T>(service) 4 ); 5 }
  • 上面的所有绑定除了最后一个,都会返回一个when/on/in语法供开发者往绑定里面加入更多的元素,比如一些限制条件、指定生效scope等等,接下来的演变如下图,只有totoDynamicValue才支持in操作,所有其走的路线是inWhenOn,其余的都是WhenOn路线:

从上图可以看出所有的调用终结于onActivation。上图的箭头给出了对应的调用关系。

而上面所讲的Binding类中赋值得到的类型和别的参数,在解析依赖的时候就会这么被用到:

将这张图片结合上面的各种to语法,你就能理解各种写法的含义了。

4、Inversify-binding-decorators和inversify-inject-decorators介绍

为了更好更丝滑地使用inversify,社区提供了下面两个工具库,来简化很多写法。

4.1、inversify-inject-decorators

该工具库主要提供了lazyInject之类的方法,除了字面上所说的惰性,另外一个非常重要的功能就是允许你将inversifyJs集成到任何自己控制类实例创建的库或者框架,比如react。什么意思呢?

比如下面这个例子:

1import { inject, injectable, named, Container } from 'inversify' 2import 'reflect-metadata' 3 4interface Weapon {} 5 6@injectable() 7class Katana implements Weapon {} 8 9@injectable() 10class Shuriken implements Weapon {} 11 12interface Ninja { 13 katana: Weapon; 14 shuriken: Weapon; 15} 16 17class Ninja implements Ninja { 18 @inject("Weapon") @named("strong") 19 public katana: Weapon; 20 @inject("Weapon") @named("weak") 21 public shuriken: Weapon; 22 public constructor() {} 23} 24 25const container = new Container() 26 27container.bind<Weapon>("Weapon").to(Katana).whenTargetNamed("strong"); 28container.bind<Weapon>("Weapon").to(Shuriken).whenTargetNamed("weak"); 29 30const ninja: Ninja = new Ninja() 31 32console.log(ninja.katana)

如果Ninja这个类不受我们控制,自己初始化了,但是又需要用到我们容器里存在的Weapon绑定,那么这个时候上面的写法是获取不到任何绑定的,只会返回undefined。

这个时候就可以考虑使用inversify-inject-decorators这个库,改写Ninja的实现:

1class Ninja implements Ninja { 2 3 @lazyInjectNamed("Weapon", "strong") 4 public katana: Weapon; 5 6 @lazyInjectNamed("Weapon", "weak") 7 public shuriken: Weapon; 8 9 public constructor() {} 10}

这样就可以获取到Weapon这个绑定了。实现的原理其实很简单,在调用getDecorators的时候把container传值进去,之后改写装饰的属性或者参数的setter和getter函数,利用container.get获取值并缓存起来,这样就完成了整个包的实现。至于第一个例子中获取不到值是因为**`injectable和inject其实都只是添加元数据,从而辅助容器在注入绑定和解析绑定的时候提供一定的信息,本质上的取值并不靠这个修饰器**

它提供的几种写法在Inversify上有其对应的:

  • @inject => @lazyInject
  • @inject @named => @lazyInjectNamed
  • @inject @tagged => @lazyInjectTagged
  • @multiInject => @lazyMultiInject

4.2、 inversify-binding-decorators

如果说上面的那个工具库是为了hack某些东西,那么这个库纯粹就是为了写更少的代码。直接引用官网的图片:

如果用原生inversify的话,需要写右边一大堆代码,如果引用这个库,那么一个修饰器就可以搞定了。最后再统一调用container.load(buildProviderModule());

原理其实也很简单,就是利用@provide收集所有的需要绑定的注入,然后统一传给container.load这个可以接受数组的方法。

除了provide,还提供了fluentProvider,后面这个可以支持原先在上一小节说的那些when/on/in的语法。比如下面这样的:

1@fluentProvide(TYPE.Weapon).whenTargetTagged("throwable", true).done(); 2class Katana implements Weapon { 3 public hit() { 4 return "cut!"; 5 } 6}

我们平时使用的时候,更喜欢将这个抽象成一个函数,比如:

1const provideThrowable = function(identifier, isThrowable) { 2 return provide(identifier) 3 .whenTargetTagged("throwable", isThrowable) 4 .done(); 5}; 6@provideThrowable(TYPE.Weapon, true) 7class Katana implements Weapon { 8 public hit() { 9 return "cut!"; 10 } 11}

这次的文章不是单纯地想介绍inversify,而是因为我们即将隆重介绍新的一代spring版nodejs框架,而该框架用到了inversify,有了这篇基础,那么在学习该框架的时候,就不会显得那么捉襟见肘了~

期待我的下篇文章吧:《初识malagu框架》

公众号关注一波~

微信公众号

关于评论和留言

如果对本文 带你学习inversify.js系列 - inversify基础知识学习(二) 的内容有疑问,请在下面的评论系统中留言,谢谢。

网站源码:linxiaowu66 · 豆米的博客

Follow:linxiaowu66 · Github