0%

22. 简单实现类似Angular的依赖注入功能

依赖注入是一个很重要的设计模式。 它使用得非常广泛,以至于几乎每个人都把它简称为 DI 。 Angular 有自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。

下面介绍一种简单的(只有100行左右代码)实现类似Angular依赖注入的方式,先看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// a.service.ts
@Injectable()
export class AService {

constructor() {

}

public doSomething() {
console.log('this is AService::doSomething');
}

}

// b.service.ts
@Injectable()
export class BService {

constructor(private readonly a: AService) {

}

public doSomething() {
this.a.doSomething();
console.log('this is BService::doSomething');
}

}

// some.module.ts
@Module({
providers: [
AService,
BService,
],
})
export class SomeModule {

}

在上面的例子中,我们创建了两个Service,其中BService依赖于AService,那么BService可以在其构造函数中声明其依赖,我们需要一种方法去自动将AService的实例注入到BService的私有只读变量a中,接下来介绍实现的步骤。

首先我们应该对Typescript进行配置,使其支持Javascript的装饰器(或者说是注解),下面是我的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"compilerOptions": {
"lib": [
"dom",
"es2015"
],
"target": "es5",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

其中,experimentalDecorators表示为ES装饰器启用实验支持emitDecoratorMetadata表示在源代码中为装饰声明产生类型的元数据,配置好了这两项之后,我们才能使用Typescript的装饰器,接下来我们需要安装依赖reflect-metadata,用于读取和设置元数据。

@Injectable的实现

@Injectable是一个装饰器,它标识被装饰的类是一个Provider,它的声明方式如下

1
2
3
4
5
export function Injectable(): ClassDecorator {
return (target) => {

};
}

我们在此装饰器中什么都不做,他只起到一个标识的作用。

@Module的实现

@Module是一个装饰器,它标识被装饰的类是一个Module,它的声明方式如下

1
2
3
4
5
6
7
8
9
const DI_IMPORTS_SYMBOL = Symbol('di:imports')
const DI_PROVIDERS_SYMBOL = Symbol('di:providers')

export function Module(options: { imports?: Array<any>, providers?: Array<any> }): ClassDecorator {
return (target) => {
Reflect.defineMetadata(DI_IMPORTS_SYMBOL, new Set(options.imports || []), target);
Reflect.defineMetadata(DI_PROVIDERS_SYMBOL, new Set(options.providers || []), target);
}
}

我们使用Set来存储一个Module作用域中它所声明的Providers和它所引入的其他模块。

Factory的实现

我们希望达到的目的是,在使用时可以通过Factory.create(SomeModule)来获取一个Module的实例,然后通过Module实例来获取一个Provider,例如Factory.create(SomeModule).get(BService).doSomething(),此时应该输出

1
2
3
Factory.create(SomeModule).get(BService).doSomething();
// this is AService::doSomething
// this is BService::doSomething

Talk is cheap. Show me the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
export namespace Factory {

export function create(module: Type) {
const imports: Set<Type> = Reflect.getMetadata(DI_IMPORTS_SYMBOL, module);
const providers: Set<any> = Reflect.getMetadata(DI_PROVIDERS_SYMBOL, module);
const providersMap = new Map();

const importModules = Array.from(imports).map((importModule) => {
let moduleInstance: ModuleInstance = moduleInstances.get(importModule);
if(!moduleInstance) {
moduleInstance = create(importModule);
moduleInstances.set(importModule, moduleInstance);
}
return moduleInstance;
});
const moduleInstance = new ModuleInstance(importModules, providersMap);

providers.forEach(provider => {
createProvider(provider, providers, moduleInstance);
});
return moduleInstance;
}

function createProvider(provider: any, providers: Set<any>, moduleInstance: ModuleInstance) {
let providerInstance = moduleInstance.providers.get(provider);

if(providerInstance) {
return providerInstance;
}

const deps: Array<any> = Reflect.getMetadata('design:paramtypes', provider);
if(!deps) {
throw new Error(`No provider named ${ provider.name }, do yout add @Injectable() to this provider?`);
}

const args = deps.map(dep => {
let depInstance = moduleInstance.providers.get(dep);
if(!depInstance) {
if(providers.has(dep)) {
depInstance = createProvider(dep, providers, moduleInstance);
} else {
moduleInstance.imports.some(imp => {
depInstance = createProvider(dep, new Set(), imp);
return !!depInstance;
});
}
}
if(!depInstance) {
throw new Error(`can not found provider ${ dep.name }`);
}
return depInstance;
});
providerInstance = new provider(...args);
moduleInstance.providers.set(provider, providerInstance);
return providerInstance;
}

export class ModuleInstance {

constructor(
public imports: Array<ModuleInstance>,
public providers: Map<any, any>) {

}

get<T>(provider: Type<T>) {
let instance: T = this.providers.get(provider);
if(!instance) {
this.imports.some(imp => {
instance = imp.get(provider);
return !!instance;
});
}
if(!instance) {
throw new Error(`No provider named: ${ provider.name }`);
}
return instance;
}
}

}

以上就是整个依赖注入的实现了,感兴趣的朋友可以到我的Github上面查看源代码,核心文件就是lib/di.ts,地址是 https://github.com/hungtcs/light-di/blob/master/lib/di.ts