咱們在開發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
複製代碼