核心

依赖注入

依赖注入是一种设计模式,用于实现控制反转。

Malagu 框架的依赖注入功能是基于Inversify实现,封装了一套更为好用的装饰器,这套装饰器设计灵感来源于Spring,对于 Java 开发者更有亲和力。

两个核心装饰器@Component@Autowired@Component相当于告诉 IoC 容器将某某类创建为一个对象(又称之为组件),并将该对象注入到 IoC 容器中进行管理,而@Autowired相当于告诉 IoC 容器我需要某某对像,请把它注入到我的对象里面来。如果您还不了解什么是依赖注入,请先阅读另一篇文档:IoC 和依赖注入

依赖注入的核心就是一系列的装饰器,下面我们将一一介绍它们。

@Component

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

示例

import { Component, Autowired } from "@malagu/core";

@Component()
export class A {}

@Component()
export class B {
  @Autowired()
  protected a: A;
}

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

参数

支持两种类型参数:id或者optionoption是一个对象,类型定义如下:

export interface ComponentOption {
  id?: ComponentId | ComponentId[];
  scope?: Scope;
  rebind?: boolean;
  proxy?: boolean;
  name?: string | number | symbol;
  tag?: { tag: string | number | symbol; value: any };
  default?: boolean;
  when?: (request: interfaces.Request) => boolean;
  sysTags?: string[];
  onActivation?: (context: interfaces.Context, t: any) => any;
}
id
string | class | symbol
import { Component } from "@malagu/core";

@Component("a")
export class A {}

id 支持stringclassSymbol,默认以类为id。为了以防重名的问题,推荐用Symbol,特别是在特大型的应用程序中。

scope
Scope

支持三种类型:Request, Singleton, Transient,默认为Singleton

  • Request:每次请求都会创建一个新的对象
  • Transient:每次请求都会创建一个新的对象
  • Singleton:单例模式,只会创建一个对象
rebind
boolean

rebind 为true时,则会替换掉容器里面的有相同id的对象。

proxy
boolean

是否开启 AOP 代理,默认不开启,也可以通过属性文件进行全局配置。

@Service

目前@Service()@Component()效果是一样的。当给服务类添加装饰器的时候,使用@Service()可读性更强;当要对服务类进行 AOP 拦截的时候,将服务类与其他类进行分开,方便 AOP 拦截。

@Autowired

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

示例

import { Component, Autowired } from "@malagu/core";

@Component()
export class FooService {
  @Autowired()
  protected barService: BarService;
}

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

参数

支持两种类型参数:id或者optionoption是一个对象,类型定义如下:

export interface AutowiredOption {
  id?: ComponentId;
  detached?: boolean;
  multi?: boolean;
}
id
string | class | symbol

id就是ComponentId,所以和@Component一样,支持stringclassSymbol,默认以类为id。为了以防重名的问题,推荐用Symbol,特别是在特大型的应用程序中。

如果不指定id,默认以属性类型为id。

detached
boolean

非托管类注入对象,比如 React 组件是不太适合注入到容器里面管理的,但是在 React 组件里面想使用容器里面的服务对象,就可以通过detached: true实现。

multi
boolean

注入多个对象,一般情况不需要特意指定,框架根据属性类型是否是数组自动判断。

@Constant

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

示例

import { Component, Autowired, Constant } from "@malagu/core";

@Constant({ id: "foo", constantValue: "bar" })
export default class {}

@Component()
export class B {
  @Autowired("foo")
  protected foo: string;
}

参数

类型定义如下:

export interface ConstantOption {
  id: ComponentId | ComponentId[];
  rebind?: boolean;
  constantValue?: any; // 你需要注入的常量值
}
id
string | class | symbol

id 支持stringclassSymbol,默认以类为id。为了以防重名的问题,推荐用Symbol,特别是在特大型的应用程序中。

rebind
boolean

rebind 为true时,则会替换掉容器里面的有相同id的对象。

constantValue
any

你需要注入的常量值。

@Optional

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

@Injectable

@Injectable 标识当前类是可注入的。 @Component  装饰器会隐式标识当前类为可注入的,所有绝大多数情况,我们无需显示指定。当有些情况,我们无法使用 @Component ,但需要标识当前类为可注入的,可以使用 @Injectable 。例如父类和抽象类的情况。

@Injectable相当于是Inversify@injectable装饰器。

@PostConstruct

@PostConstruct 用在类方法上。如果我们通过 @Component 将类的对象托管给容器管理,容器将负责类对象的生命周期的管理。在很多情况下,我们希望类对象创建创建成功后(包括需要自动注入的对象属性也完成了注入动作)做一些初始化的动作。这种情况,我们可以使用 @PostConstruct

@Named

@Named 用在类上。给托管到容器的类对象取一个名字,在注入的时候,可以配合 @TargetName 按照组件 ID + 名字来注入需要的对象。

@TargetName

给需要注入的属性或者构造函数参数设置一个目标名称,这样可以通过注入上下文可以获取该目标名称。

@Tagged

@Named@Autowired 配合使用。在注入的时候,可以配合按照组件 ID + tag 来注入需要的类对象。类对象的 tag 可以通过 @Component 的 tag 属性指定。

Detached 型装饰器

分别为@Autowired@Value提供了两个用于非托管场景的简化装饰器,名字是一样的,只是所在的包不一样。这样在使用的时候就不需要制定参数detached: true。如下:

// 非托管场景使用
import { autowired, value } from "@malagu/core/lib/common/annotation/detached";
import { autorpc } from "@malagu/rpc/lib/common/annotation/detached";

也可以使用@malagu/core下面的 Valueautowired,但是必须要制定参数

最佳实践

Malagu 框架基于面向对象编程原则来设计,所以推荐大家在真实的业务场景也能遵循面向对象编程原则,但不强制。

我们通过@Component注入对象到容器的时候,需要告诉容器,我们的注入对象的 id 是什么,只有这样我们才能知道通过什么 id 取回我们的注入对象。

在面向对象编程原则中,有一条原则是:面向接口编程,所以我们应该以接口作为注入对象的 id,取也以接口取,这样我们的代码就是面向接口来实现,至于接口背后实现,对象使用者不需要关心,只需要操作接口即可,所以对象使用者不依赖接口的具体实现。

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

// 推荐使用 Symbol 类型,可以保证 id 唯一
export const Application = Symbol("Application");

export interface Application {
  start(): Promise<void>;
}

Copyright © 2024 Zero (github@groupguanfang) 粤ICP备2023102563号