Page Contents

什么是Binding?

Binding译为“绑定”,下略。

Binding可以在一个实例化后的Context(上下文)中作为一个或多个对象的代表。一个Binding使用一个不重复的键作为地址,在Context中可以通过此地址获取到Binding所对应的值。

Binding的属性

一个Binding通常拥有以下属性:

  • key(键): 在上下文中,每个Binding都有一个用于标记自己的唯一键;
  • scope(范围): 在上下文中,用来控制如何创建或缓存Binding的值;
  • tags(标签): 可以是名称字符串或名称-值键值对,用来描述或注释一个Binding
  • value(值): 每个Binding必须设置一个可以解析出绑定的值的提供装置(比如,类、方法、常量等),这样Binding才可以被解析成一个常量或动态值;

Binding

如何创建一个Binding

有几种方式可以创建一个Binding

  • 使用Binding类的构造器

    import {Context, Binding} from '@loopback/core';
    // 实例化一个上下文
    const context = new Context();
    // 实例化一个绑定,并将其'key'属性设置为'my-key'
    const binding = new Binding('my-key');
    // 将绑定添加至上下文中
    ctx.add(binding);
    
  • 使用Binding类的.bind()静态方法

    import {Context, Binding} from '@loopback/core';
    // 实例化一个上下文
    const context = new Context();
    // 实例化一个绑定,并将其'key'属性设置为'my-key'
    const binding = Binding.bind('my-key');
    // 将绑定添加至上下文中
    ctx.add(binding);
    
  • 使用Context类的.bind()方法

    import {Context, Binding} from '@loopback/core';
    // 实例化一个上下文
    const context = new Context();
    // 在上下文中添加一个绑定,并将其'key'属性设置为'my-key'
    context.bind('my-key');
    

</div>

如何设置一个Binding?

Binding类通过一套流畅的API提供了有关Binding的创建和配置的操作。

解析一个Binding的值的形式

Binding可以通过多种形式以解析出一个具体的值。具体如下:

常量形式(Constant)

适用场景:在解析一个Binding时,Binding的值是一个固定的值。
比如,Binding的值是一个字符串(String)、一个方法(Function)、一个对象(Object)、一个数组(Array)或者任何其他类型的值。

binding.to('my-value');

请注意,在常量形式下为了避免混淆,值的类型不能是Promise

工厂形式(Factory Function)

适用场景:在解析一个Binding时,Binding的值是需要被动态计算的。
比如,Binding的值是当前系统时间、Binding的值是远程接口的返回值、Binding的值是远程数据库的数据等。

binding.toDynamicValue(() => 'my-value');
binding.toDynamicValue(() => new Date());
binding.toDynamicValue(() => Promise.resolve('my-value'));

工厂方法可以接收一个涵盖了上下文信息、绑定信息和解析选项信息的参数。

import {ValueFactory} from '@loopback/core';

// 现在可以通过工厂方法的第一个传参获取到解析时的相关信息了
const factory: ValueFactory<string> = resolutionCtx => {
  return `Hello, ${resolutionCtx.context.name}#${
    resolutionCtx.binding.key
  } ${resolutionCtx.options.session?.getBindingPath()}`;
};
const b = ctx.bind('msg').toDynamicValue(factory);

通过 解构赋值(Destructuring assignment) 可以进一步简化并快速访问到contextbindingoptions等对象。

const factory: ValueFactory<string> = ({context, binding, options}) => {
  return `Hello, ${context.name}#${
    binding.key
  } ${options.session?.getBindingPath()}`;
};

进阶用法:使用内置了静态方法value的类,该静态方法允许参数注入。(具体见提供器形式(Provider)

import {inject} from '@loopback/core';

class GreetingProvider {
  static value(@inject('user') user: string) {
    return `Hello, ${user}`;
  }
}

const b = ctx.bind('msg').toDynamicValue(GreetingProvider);

类形式(Class)

适用场景:在解析一个Binding时,Binding的值是一个类的实例化对象。
比如,控制器(Controller)等。Binding的值可以是一个被实例化的类。依赖注入通常用于影响被注入依赖的类的内部成员对象身上。

class MyController {
  constructor(@inject('my-options') private options: MyOptions) {
    // ...
  }
}

binding.toClass(MyController);

提供器形式(Provider)

适用场景:在解析一个Binding时,用于解析Binding的值的工厂方法需要用到依赖注入(Dependency Injection)。
提供器指的是,内置了value()方法的类,该方法可以在实例化后用来解析Binding的值。

class MyValueProvider implements Provider<string> {
  constructor(@inject('my-options') private options: MyOptions) {
    // ...
  }

  value() {
    return this.options.defaultValue;
  }
}

binding.toProvider(MyValueProvider);

提供器被当做一个内置了依赖注入功能的空壳,可以理解为是一个进阶版本的工厂方法。如果工厂方法没有使用到依赖注入,则直接使用普通的工厂方法和toDynamicValue()方法即可。

同族形式(Alias)

适用场景:在解析一个Binding时,此Binding的值的来源是另外一个Binding的值。
同族指的是,一个允许携带可选路径的键值,这个键值可以用来从另外一个Binding上解析出值。 比如,我们在设置Api Explorer(API浏览页面)的时候,需要用到RestServer对象的相关属性,我们可以创建一个Binding并将它通过.toAlias()方法设置为keyservers.RestServer.options#apiExplorerBinding的同族。

// 创建`key`为`servers.RestServer.options`的绑定
ctx.bind('servers.RestServer.options').to({apiExplorer: {path: '/explorer'}});
// 创建`key`为`apiExplorer.options`的绑定
ctx
  .bind('apiExplorer.options')
// 声明此绑定是`key`为`servers.RestServer.options`的绑定的同族
  .toAlias('servers.RestServer.options#apiExplorer');
const apiExplorerOptions = await ctx.get('apiExplorer.options'); // => {path: '/explorer'}

配置BindingSocpe

Socpe译为“作用域”,下略。

一个Binding可以为多个请求提供解析值,通过Binding所在的Context对象的.get().getSync()方法,或者依赖注入。 在解析Binding的值时,Scope用来控制解析过程的策略。即可以返回一个新的值,或为从属于同一层级的Context的多个请求返回一个相同的值。
如下示例中,虽然Binding都是"my-key",但是value1变量和value2变量的值可能是相同的或不同的,这都取决于BindingScope是如何设置的。

const value1 = await ctx.get('my-key');
const value2 = ctx.getSync('my-key');

在相同的Context中解析一个Binding时,我们允许使用如下的Scope

  • BindingScope.TRANSIENT(临时作用域,此项为默认值)
  • BindingScope.CONTEXT(同级作用域)
  • BindingScope.SINGLETON(常态作用域)

请参见BindingScope.

binding.inScope(BindingScope.SINGLETON);

BindingScope可以通过BindingScope枚举引用到。

配置正确的Scope

在配置BindingScope时,请先思考如下问题:

  1. 在解析Binding的值时,是否有必要为每个请求都返回一个新的值?
  2. 在解析Binding的值时,是否解析值会得到保留,或是因请求不同而变化?

请注意,使用了Binding.to()方法的Binding的值不会受到Scope的影响,如下所示:

ctx.bind('my-name').to('John Smith');

key属性为'my-name'Binding的值将被永远解析为'John Smith'

Scope只会影响到使用.toDynamicValue().toClass().toProvider()方法提供解析值的Binding

假设我们需要满足一个需求:需要创建一个可以提供当前系统日期的Binding

ctx
  .bind('current-date')
  .toDynamicValue(() => new Date());
const d1 = ctx.getSync('current-date');
const d2 = ctx.getSync('current-date');
// d1 !== d2

在上面的代码中,BindingScope的默认值为TRANSIENT,即临时作用域。 d1d2的值都是来源于new Date()方法,并且都是通过ctx.getSync('current-date')方法解析出来的。两个不同的日期被分配到了d1d2的值上,相当于每次解析Binding的值时,值都会成为解析时的系统日期。

ctx
  .bind('current-date')
  .toDynamicValue(() => new Date())
  .inScope(BindingScope.SINGLETON);
const d1 = ctx.getSync<Date>('current-date');
const d2 = ctx.getSync<Date>('current-date');
// d1 === d2

在上面的代码中,BindingScope的值被设置为SINGLETON,即常态作用域。自然就可以推理出d1d2的值都是相同的日期,也就是不符合我们的需求。