咱们在开发node应用时,一般会遇到装饰器来实现依赖注入,那么用装饰器实现依赖注入背后实现的原理是什么?node
提到依赖注入就必须提到Ioc容器,提到Reflect Metadata,接下来咱们来详细理解一下这些概念git
控制反转(Inversion of Control,缩写为IoC),是面向对象程序设计中一种核心的设计原则,通常状况下,大多数应用程序中都会依赖其余对象,原来的实现过程都是靠自身程序调用实现,高耦合度,调试和开发成本很高,好比,传统的实现方式:github
// 常见的依赖
import {A} from './A';
import {B} from './B';
class C {
consturctor() {
this.a = new A();
this.b = new B(this.a);
}
}
复制代码
为了下降耦合度,社区提出一个方案,创建一个对象池来维护这些依赖,而且在应用初始化的时候自动处理相关依赖,并将类进行实例化,这个对象池咱们称之为Ioc容器,容器提供一些基本功能,好比注册,删除,获取等基本操做,midwayjs团队在社区方案的基础上进行了封装,推出了injection包,injection中相关源码以下:express
export class ObjectDefinitionRegistry extends Map implements IObjectDefinitionRegistry {
private singletonIds = [];
get identifiers() {
const ids = [];
for (const key of this.keys()) {
if (key.indexOf(PREFIX) === -1) {
ids.push(key);
}
}
return ids;
}
get count() {
return this.size;
}
getSingletonDefinitionIds(): ObjectIdentifier[] {
return this.singletonIds;
}
getDefinitionByName(name: string): IObjectDefinition[] {
const definitions = [];
for (const v of this.values()) {
const definition = <IObjectDefinition> v;
if (definition.name === name) {
definitions.push(definition);
}
}
return definitions;
}
registerDefinition(identifier: ObjectIdentifier, definition: IObjectDefinition) {
if (definition.isSingletonScope()) {
this.singletonIds.push(identifier);
}
this.set(identifier, definition);
}
getDefinition(identifier: ObjectIdentifier): IObjectDefinition {
return this.get(identifier);
}
getDefinitionByPath(path: string): IObjectDefinition {
for (const v of this.values()) {
const definition = <IObjectDefinition> v;
if (definition.path === path) {
return definition;
}
}
return null;
}
removeDefinition(identifier: ObjectIdentifier): void {
this.delete(identifier);
}
hasDefinition(identifier: ObjectIdentifier): boolean {
return this.has(identifier);
}
clearAll(): void {
this.clear();
}
hasObject(identifier: ObjectIdentifier): boolean {
return this.has(PREFIX + identifier);
}
registerObject(identifier: ObjectIdentifier, target: any) {
this.set(PREFIX + identifier, target);
}
getObject(identifier: ObjectIdentifier): any {
return this.get(PREFIX + identifier);
}
}
复制代码
而在对象中实际应用Ioc容器时,一般有两种应用形式:依赖注入及依赖查找,npm
实现控制反转主要有两种方式:依赖注入和依赖查找。二者的区别在于,前者是被动的接收对象,在类A的实例建立过程当中即建立了依赖的B对象,经过类型或名称来判断将不一样的对象注入到不一样的属性中,然后者是主动索取相应类型的对象,得到依赖对象的时间也能够在代码中自由控制。json
基于接口。实现特定接口以供外部容器注入所依赖类型的对象。markdown
基于 set 方法。实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。app
基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。框架
基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不须要显式的定义以上三种代码,即可以让外部容器传入对应的对象。该方案至关于定义了public的set方法,可是由于没有真正的set方法,从而不会为了实现依赖注入致使暴露了不应暴露的接口(由于set方法只想让容器访问来注入而并不但愿其余依赖此类的对象访问)。ide
// 使用 IoC import {Container} from 'injection'; import {A} from './A'; import {B} from './B'; const container = new Container(); container.bind(A); container.bind(B);
class C { consturctor() { this.a = container.get('A'); this.b = container.get('B'); } }
若是每次代码都手动写入依赖,实在太繁琐,并且不必,因此通常的框架语言,好比angular,midway等使用了装饰器,装饰器的使用离不开Reflect Metadata,Reflect Metadat是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。
装饰器是一种特殊类型的声明,装饰器自己是一个函数,它可以被附加到类声明,方法,访问符(getter, setter),属性或参数上。装饰器采用@expression
这种形式进行使用。分为类装饰器,参数装饰器,方法、属性、访问器的装饰器。不一样的装饰器使用的场景和做用不一样。
4.1 类装饰器,声明在类定义以前,接收的参数是类自己,用来监控,修改或者替换类定义,使用方法以下:
function provide(identifier?: ObjectIdentifier) {
// 这里的target默认是紧邻着的DemoClass
return function (target: any) {
// .....省略操做
return target;
};
}
@provide()
class DemoClass {}
复制代码
4.2 参数装饰器,声明在一个参数声明以前。运行时当作函数被调用,这个函数接收下面三个参数:
使用方法以下:
function paramsDecorator(target: string, key: string, index: number) {
// 这里的target,DemoClass.propertype
// key: getList
// index: 0
}
class DemoClass {
getList(@paramsDecorator pageId: string){// ...}
}
复制代码
4.3 方法、属性、访问器装饰器,声明在一个方法、属性、访问器以前,运行时看成函数执行,接收的参数以下:
function inject(identifier?: ObjectIdentifier) {
return function (target: any, targetKey: string, index?: number): void {
if (typeof index === 'number') {
if (!identifier) {
const args = getParamNames(target);
if (target.length === args.length && index < target.length) {
identifier = args[index];
}
}
const metadata = new Metadata(INJECT_TAG, identifier);
tagParameter(target, targetKey, index, metadata);
} else {
if (!identifier) {
identifier = targetKey;
}
const metadata = new Metadata(INJECT_TAG, identifier);
tagProperty(target, targetKey, metadata);
}
};
}
class Parent {
// target: parent.propertype
// key: ctx
// description: PropertyDescriptor类型
@inject()
ctx: FaaSContext;
}
复制代码
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只须要:
npm i reflect-metadata --save
。tsconfig.json
里配置 emitDecoratorMetadata
选项。因为reflect-meta库封装了一些API,能够访问类或者类属性,因此在装饰器使用时,能够操做类或者类属性添加或获取元数据,midway中的一些装饰器是基于Reflect Metadata实现的,以provide为例
import 'reflect-metadata';
import {DUPLICATED_INJECTABLE_DECORATOR} from '../utils/errMsg';
import { initOrGetObjectDefProps, TAGGED_CLS } from '..';
import {ObjectIdentifier, TagClsMetadata} from '../interfaces';
const camelCase = require('camelcase');
function provide(identifier?: ObjectIdentifier) {
return function (target: any) {
// 检测是否已有此元数据,即此类是否注册到Ioc容器中,是否有重名
if (Reflect.hasOwnMetadata(TAGGED_CLS, target)) {
throw new Error(DUPLICATED_INJECTABLE_DECORATOR);
}
if (!identifier) {
identifier = camelCase(target.name);
}
// 若是没有注册,则将此target
Reflect.defineMetadata(TAGGED_CLS, {
id: identifier,
originName: target.name,
} as TagClsMetadata, target);
// init property here
initOrGetObjectDefProps(target);
return target;
};
}
复制代码
当调用provide装饰器时,此类会被Ioc容器自动扫描,并在容器上绑定定义,对应的是container.bind,同理调用@inject时,将容器中的定义实例化成一个对象,而且绑定到属性中,调用的时候就能够访问到该属性,详情可参考inject实现源码
有了上述的基础知识,就不难理解依赖注入了,咱们来看一个例子以及编译以后的代码
import { provide, inject, FaaSContext } from '@ali/midway-faas';
import { InjectEntityModel } from '@midwayjs/orm';
import { Repository, DeepPartial } from 'typeorm';
import { CommonFieldStatus } from '../common/enum';
import { ERROR, throwError } from '../common/error';
import { Node } from '../model/node';
import { Operation, TargetType, OperationActionType } from '../model/operation';
import { StructureService } from '../service/structureService';
import { OperationService } from '../service/operationService';
@provide()
export class NodeService {
@inject()
ctx: FaaSContext;
@inject()
operationService: OperationService;
@inject()
structureService: StructureService;
@InjectEntityModel(Node)
repository: Repository<Node>;
// .....
}
复制代码
编译以后:
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeService = void 0;
const midway_faas_1 = require("@ali/midway-faas");
const orm_1 = require("@midwayjs/orm");
const typeorm_1 = require("typeorm");
const enum_1 = require("../common/enum");
const error_1 = require("../common/error");
const node_1 = require("../model/node");
const operation_1 = require("../model/operation");
const structureService_1 = require("./structureService");
const operationService_1 = require("./operationService");
let NodeService = class NodeService {
// 逻辑代码
};
__decorate([
midway_faas_1.inject(),
__metadata("design:type", Object)
], NodeService.prototype, "ctx", void 0);
__decorate([
midway_faas_1.inject(),
__metadata("design:type", operationService_1.OperationService)
], NodeService.prototype, "operationService", void 0);
__decorate([
midway_faas_1.inject(),
__metadata("design:type", structureService_1.StructureService)
], NodeService.prototype, "structureService", void 0);
__decorate([
orm_1.InjectEntityModel(node_1.Node),
__metadata("design:type", typeorm_1.Repository)
], NodeService.prototype, "repository", void 0);
NodeService = __decorate([
midway_faas_1.provide()
], NodeService);
exports.NodeService = NodeService;
//# sourceMappingURL=nodeService.js.map
复制代码