Malagu 框架的依赖注入功能是基于 InversifyJS 实现,封装了一套更为好用的装饰器,这套装饰器设计灵感来源于 Spring,对于 Java 开发者更有亲和力。两个核心装饰器 @Component
和 @Autowired
, @Component
相当于告诉 IoC 容器将某某类创建为一个对象(又称之为组件),并将该对象注入到 IoC 容器中进行管理,而 @Autowired
相当于告诉 IoC 容器我需要某某对像,请把它注入到我的对象里面来。
@Component
用于类上,将类实例化注入到容器中,其他对象可以从容器中取出来使用,通过 @Autowired 可以很方便的从容器中取出需要的对象。
示例
import { Component, Autowired } from '@malagu/core'; @Component() export class A { } @Component() export class B { @Autowired() protected a: A; }
说明:如果不提供 id 的话,默认以类为 id。
参数
支持两种类型参数:id 或者 option,
- 只提供 id 方式如下:
import { Component } from '@malagu/core'; @Component('a') export class A { }
id 支持 string、class 和 Symbol,推荐用 Symbol。
- option 方式, option 类型定义如下:
export interface ComponentOption { id?: ComponentId | ComponentId[]; // 组件的 ID 标识,默认以类为 id scope?: Scope; // 支持三种类型:Request, Singleton, Transient,默认为 Singleton rebind?: boolean; // rebind 为 true 则会替换掉容器里面的有相同 id 的对象 proxy?: boolean; // 是否开启 AOP 代理,默认不开启,也可以通过属性文件进行全局配置 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; }
使用方式如下:
import { Component } from '@malagu/core'; @component({id: ApplicationShell, rebind: true }) export class A { }
@Autowired
可用于类的成员属性或构造方法参数上,告诉容器将需要的对象注入到添加了 @Autowired
装饰器的类成员属性或构造方法参数上。
示例
import { Component, Autowired } from '@malagu/core'; @Component() export class B { @Autowired() protected a: A; }
如果不提供需要注入的组件 id 的话,默认以属性类型作为 id。
参数
支持两种类型参数:id 或者 option。
- 只提供 id 方式如下:
使用方式如下:
import { Component, Autowired } from '@malagu/core'; @component('a') export class A { } @component() export class B { @autowired('a') protected a: A; }
说明:id 支持 string、class 和 Symbol,推荐用 Symbol.
- option 方式, option 类型定义如下:
export interface AutowiredOption { id?: ComponentId; // 组件的 ID 标识,默认以属性类型为 id detached?: boolean; // 非托管类注入对象,比如 React 组件是不太适合注入到容器里面管理的,但是在 React 组件里面想使用容器里面的服务对象,就可以通过 detached: true 实现 multi?: boolean; // 注入多个对象,一般情况不需要特意指定,框架根据属性类型是否是数组自动判断 }
使用方式如下:
// detached 为 true 的时候,不需要加 @Component() import { Autowired } from '@malagu/core'; export class B { @Autowired({ detached: true }) protected a: A; }
@Value
把 Malagu 组件属性注入到对象中。
示例
从 Malagu 组件属性中取出 foo
属性的值,注入到A 类对象的 foo
成员属性中(成员属性名不一定要取名 foo
)。
import { Component, Value } from '@malagu/core'; @Component() export class A { @Value('foo') // 支持 EL 表达式语法,如 @Value('obj.xxx')、@Value('arr[1]') 等等 protected foo: string; }
如果不提供需要注入的 EL 表达式的话,默认以属性名称作为 EL 表达式。
参数
支持两种类型参数:el 或者 option。
- 只提供 el 方式如下:
import { Component, Value } from '@malagu/core'; @Component() export class A { @Value('foo') protected foo: string; }
- option 方式, option 类型定义如下:
export interface ValueOption { el?: string; // 表达式规则请参考:https://github.com/TomFrost/jexl detached?: boolean; // 非托管类注入组件,比如 React 组件是不太适合注入到容器里面管理的,但是在 React 组件里面想使用容器里面的配置对象,就可以通过 detached: true 实现 }
使用方式如下:
import { Value } from '@malagu/core'; // detached 为 true 的时候,不需要加 @Component() export class A { @Value({ el: 'foo', detached: true }) protected foo: string; }
@Aspect
用于类上,基于 @Component
扩展实现,在 @Component
的属性配置只是扩展了 pointcut
属性,对指定系统 tag 进行方法拦截。 @Aspect
可以方便我们定义和实现方法拦截功能的,所有该装饰器只能在与方法拦截相关的接口实现类上。
示例
import { Aspect } from '@malagu/core'; @Aspect({ id: MethodBeforeAdvice, pointcut }) export class SecurityMethodBeforeAdivice extends AbstractSecurityMethodAdivice implements MethodBeforeAdvice { async before(method: string | number | symbol, args: any[], target: any): Promise<void> { if (this.needAccessDecision(method)) { const ctx = { method, args, target, authorizeType: AuthorizeType.Pre, grant: 0 }; const securityMetadata = await this.securityMetadataSource.load(ctx); await this.accessDecisionManager.decide(securityMetadata); } } }
参数
export interface AspectOption extends ComponentOption { id: ComponentId; pointcut?: string; // 需要被拦截的切点,切点的值与 @Component 的 sysTags 关联 }
@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; // 你需要注入的常量值 }
Optional
@Autowired
告诉容器将需要的对象注入对应的类成员属性或构造方法参数上。当发现需要注入的对象在容器中没有找到则会报错,假如我们想让对象在容器中找不到时不报错,我们就可以通过指定 @Optional
装饰器来实现,告诉容器找不到注入对象就算了。
@Injectable
@Injectable
标识当前类是可注入的。 @Component
装饰器会隐式标识当前类为可注入的,所有绝大多数情况,我们无需显示指定。当有些情况,我们无法使用 @Component
,但需要标识当前类为可注入的,可以使用 @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
下面的 Value
、 autowired
,但是必须要制定参数
最佳实践
Malagu 框架基于面向对象编程原则来设计,所以推荐大家在真实的业务场景也能遵循面向对象编程原则,但不强制。我们通过 @Component
注入对象到容器的时候,需要告诉容器,我们的注入对象的 id 是什么,只有这样我们才能知道通过什么 id 取回我们的注入对象。在面向对象编程原则中,有一条原则是:面向接口编程,所以我们应该以接口作为注入对象的 id,取也以接口取,这样我们的代码就是面向接口来实现,至于接口背后实现,对象使用者不需要关心,只需要操作接口即可,所以对象使用者不依赖接口的具体实现。
需要注意的是在 TypeScript 中,接口的声明是编译时的,而不是运行时的,所以接口在运行时没有真实的对象与之对应,这种情况下,我们通常会再多声明一个与接口同名称的对象,作为接口在运行时的真正对象,这样我们就可以看起来像以接口作为注入对象的 id 了。如下:
export const Application = Symbol('Application'); // 推荐使用 Symbol 类型,可以保证 id 唯一 export interface Application { start(): Promise<void>; }