Malagu 框架的依赖注入功能是基于 InversifyJS 实现,封装了一套更为好用的装饰器,这套装饰器设计灵感来源于 Spring,对于 Java 开发者更有亲和力。两个核心装饰器 @Component@Autowired@Component 相当于告诉 IoC 容器将某某类创建为一个对象(又称之为组件),并将该对象注入到 IoC 容器中进行管理,而 @Autowired 相当于告诉 IoC 容器我需要某某对像,请把它注入到我的对象里面来。

@Component

用于类上,将类实例化注入到容器中,其他对象可以从容器中取出来使用,通过 @Autowired 可以很方便的从容器中取出需要的对象。

示例

  1. @Component()
  2. export class A {
  3. }
  4. @Component()
  5. export class B {
  6. @Autowired()
  7. protected a: A;
  8. }

说明:如果不提供 id 的话,默认以类为 id。

参数

支持两种类型参数:id 或者 option,

  1. 只提供 id 方式如下:
  1. @Component('a')
  2. export class A {
  3. }

id 支持 string、class 和 Symbol,推荐用 Symbol。

  1. option 方式, option 类型定义如下:
  1. export interface ComponentOption {
  2. id?: interfaces.ServiceIdentifier<any>; // 组件的 ID 标识,默认以类为 id
  3. scope?: Scope; // 支持三种类型:Request, Singleton, Transient,默认为 Singleton
  4. rebind?: boolean; // rebind 为 true 则会替换掉容器里面的有相同 id 的对象
  5. proxy?: boolean; // 是否开启 AOP 代理,默认不开启
  6. }

使用方式如下:

  1. @component({id: ApplicationShell, rebind: true })
  2. export class A {
  3. }

@Autowired

可用于类的成员属性或构造方法参数上,告诉容器将需要的对象注入到添加了 @Autowired 装饰器的类成员属性或构造方法参数上。

示例

  1. @Component()
  2. export class B {
  3. @Autowired()
  4. protected a: A;
  5. }

如果不提供需要注入的组件 id 的话,默认以属性类型作为 id。

参数

支持两种类型参数:id 或者 option。

  1. 只提供 id 方式如下:

使用方式如下:

  1. @component('a')
  2. export class A {
  3. }
  4. @component()
  5. export class B {
  6. @autowired('a')
  7. protected a: A;
  8. }

说明:id 支持 string、class 和 Symbol,推荐用 Symbol.

  1. option 方式, option 类型定义如下:
  1. export interface AutowiredOption {
  2. id?: ServiceIdentifierOrFunc; // 组件的 ID 标识,默认以属性类型为 id
  3. detached?: boolean; // 非托管类注入对象,比如 React 组件是不太适合注入到容器里面管理的,但是在 React 组件里面想使用容器里面的服务对象,就可以通过 detached: true 实现
  4. }

使用方式如下:

  1. // detached 为 true 的时候,不需要加 @Component()
  2. export class B {
  3. @Autowired({ detached: true })
  4. protected a: A;
  5. }

@Optional

@Autowired 告诉容器将需要的对象注入对应的类成员属性或构造方法参数上。当发现需要注入的对象在容器中没有找到则会报错,假如我们想让对象在容器中找不到时不报错,我们就可以通过指定 @Optional 装饰器来实现,告诉容器找不到注入对象就算了。

@Value

把 Malagu 组件属性注入到对象中。

示例

从 Malagu 组件属性中取出 foo 属性的值,注入到A 类对象的 foo 成员属性中(成员属性名不一定要取名 foo )。

  1. @Component()
  2. export class A {
  3. @Value('foo') // 支持 EL 表达式语法,如 @Value('obj.xxx')、@Value('arr[1]') 等等
  4. protected foo: string;
  5. }

如果不提供需要注入的 EL 表达式的话,默认以属性名称作为 EL 表达式。

参数

支持两种类型参数:el 或者 option。

  1. 只提供 el 方式如下:
  1. @Component()
  2. export class A {
  3. @Value('foo')
  4. protected foo: string;
  5. }
  1. option 方式, option 类型定义如下:
  1. export interface ValueOption {
  2. el?: string; // 表达式规则请参考:https://github.com/TomFrost/jexl
  3. detached?: boolean; // 非托管类注入组件,比如 React 组件是不太适合注入到容器里面管理的,但是在 React 组件里面想使用容器里面的配置对象,就可以通过 detached: true 实现
  4. }

使用方式如下:

  1. // detached 为 true 的时候,不需要加 @Component()
  2. export class A {
  3. @value({ el: 'foo', detached: true })
  4. protected foo: string;
  5. }

Detached 型装饰器

分别为 @Autowired@Value 提供了两个用于非托管场景的简化装饰器,名字是一样的,只是所在的包不一样。如下:

  1. // 非托管场景使用
  2. import { autorpc, autowired, value } from '@malagu/core/lib/common/annotation/detached';
  3. // 通用场景使用,在非托管场景下,需要指明 detached 参数,如 @value({ detached: true })
  4. import { autorpc, autowired, value } from '@malagu/core/lib/common/annotation';

最佳实践

Malagu 框架基于面向对象编程原则来设计,所以推荐大家在真实的业务场景也能遵循面向对象编程原则,但不强制。我们通过 @Component 注入对象到容器的时候,需要告诉容器,我们的注入对象的 id 是什么,只有这样我们才能知道通过什么 id 取回我们的注入对象。在面向对象编程原则中,有一条原则是:面向接口编程,所以我们应该以接口作为注入对象的 id,取也以接口取,这样我们的代码就是面向接口来实现,至于接口背后实现,对象使用者不需要关心,只需要操作接口即可,所以对象使用者不依赖接口的具体实现。

需要注意的是在 TypeScript 中,接口的声明是编译时的,而不是运行时的,所以接口在运行时没有真实的对象与之对应,这种情况下,我们通常会再多声明一个与接口同名称的对象,作为接口在运行时的真正对象,这样我们就可以看起来像以接口作为注入对象的 id 了。如下:

  1. export const Application = Symbol('Application'); // 推荐使用 Symbol 类型,可以保证 id 唯一
  2. export interface Application {
  3. start(): Promise<void>;
  4. }