diff --git a/.gitignore b/.gitignore index 51a6513b27e5..fd5e1b6e602c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ package-lock.json .nodejs-cache .tmp packages/midway/_package.json +midway_benchmark_app \ No newline at end of file diff --git a/packages/core/src/common/reflectTool.ts b/packages/core/src/common/reflectTool.ts index 500702118f8c..5694346ef8ff 100644 --- a/packages/core/src/common/reflectTool.ts +++ b/packages/core/src/common/reflectTool.ts @@ -59,6 +59,14 @@ export function recursiveGetPrototypeOf(target: any): any[] { return properties; } +export function getOwnMetadata( + metadataKey: any, + target: any, + propertyKey?: string | symbol +): ReflectResult { + return Reflect.getOwnMetadata(metadataKey, target, propertyKey); +} + /** * get metadata value of a metadata key on the prototype chain of an object and property * @param metadataKey metadata's key diff --git a/packages/core/src/context/midwayContainer.ts b/packages/core/src/context/midwayContainer.ts index f7f8b3a83fa4..4ea7657ac8c8 100644 --- a/packages/core/src/context/midwayContainer.ts +++ b/packages/core/src/context/midwayContainer.ts @@ -24,6 +24,8 @@ import { ASPECT_KEY, listPreloadModule, isProxy, + INJECT_CLASS_KEY_PREFIX, + DecoratorManager, ResolveFilter, isRegExp, } from '@midwayjs/decorator'; @@ -46,7 +48,7 @@ import { run } from '@midwayjs/glob'; import * as pm from 'picomatch'; import { BaseApplicationContext } from './applicationContext'; import * as util from 'util'; -import { recursiveGetMetadata } from '../common/reflectTool'; +import { getOwnMetadata, recursiveGetPrototypeOf } from '../common/reflectTool'; import { ObjectDefinition } from '../definitions/objectDefinition'; import { FunctionDefinition } from '../definitions/functionDefinition'; import { ManagedReference, ManagedValue } from './managed'; @@ -323,17 +325,46 @@ export class MidwayContainer } // inject properties - const metaDatas = recursiveGetMetadata(TAGGED_PROP, target); + const props = recursiveGetPrototypeOf(target); + props.push(target); + definitionMeta.properties = []; - for (const metaData of metaDatas) { - this.debugLogger(` inject properties => [${Object.keys(metaData)}]`); - for (const metaKey in metaData) { - for (const propertyMeta of metaData[metaKey]) { - definitionMeta.properties.push({ - metaKey, - args: propertyMeta.args, - value: propertyMeta.value, - }); + definitionMeta.handlerProps = []; + for (const p of props) { + const metaData = getOwnMetadata(TAGGED_PROP, p); + + if (metaData) { + this.debugLogger(` inject properties => [${Object.keys(metaData)}]`); + for (const metaKey in metaData) { + for (const propertyMeta of metaData[metaKey]) { + definitionMeta.properties.push({ + metaKey, + args: propertyMeta.args, + value: propertyMeta.value, + }); + } + } + } + + const meta = getOwnMetadata(INJECT_CLASS_KEY_PREFIX, p) as any; + if (meta) { + for (const [key, vals] of meta) { + if (Array.isArray(vals)) { + for (const val of vals) { + if ( + val.key !== undefined && + val.key !== null && + typeof val.propertyName === 'string' + ) { + definitionMeta.handlerProps.push({ + handlerKey: DecoratorManager.removeDecoratorClassKeySuffix( + key + ), + prop: val, + }); + } + } + } } } } @@ -410,6 +441,7 @@ export class MidwayContainer definition.destroyMethod = definitionMeta.destroyMethod; definition.scope = definitionMeta.scope; definition.autowire = definitionMeta.autowire; + definition.handlerProps = definitionMeta.handlerProps; this.registerDefinition(definitionMeta.id, definition); } diff --git a/packages/core/src/context/resolverHandler.ts b/packages/core/src/context/resolverHandler.ts index 90606400c1d0..6dc820498103 100644 --- a/packages/core/src/context/resolverHandler.ts +++ b/packages/core/src/context/resolverHandler.ts @@ -2,12 +2,12 @@ import { CLASS_KEY_CONSTRUCTOR, getClassMetadata } from '@midwayjs/decorator'; import { ManagedResolverFactory } from './managedResolverFactory'; import { MidwayContainer } from './midwayContainer'; import * as util from 'util'; -import { HandlerFunction, IResolverHandler } from '../interface'; - -interface FrameworkDecoratorMetadata { - key: string; - propertyName: string; -} +import { + HandlerFunction, + IResolverHandler, + FrameworkDecoratorMetadata, + IObjectDefinition, +} from '../interface'; const debug = util.debuglog('midway:container'); @@ -56,19 +56,16 @@ export class ResolverHandler implements IResolverHandler { * @param context 上下文 * @param definition 定义 */ - afterEachCreated(instance, context, definition) { - const iter = this.handlerMap.keys(); - for (const key of iter) { - // 处理配置装饰器 - const setterProps: FrameworkDecoratorMetadata[] = getClassMetadata( - key, - instance - ); - this.defineGetterPropertyValue( - setterProps, - instance, - this.getHandler(key) - ); + afterEachCreated(instance, context, definition: IObjectDefinition) { + if (this.handlerMap.size > 0 && Array.isArray(definition.handlerProps)) { + // 已经预先在 bind 时处理 + for (const item of definition.handlerProps) { + this.defineGetterPropertyValue( + item.prop, + instance, + this.getHandler(item.handlerKey) + ); + } } } /** @@ -79,19 +76,17 @@ export class ResolverHandler implements IResolverHandler { * @param getterHandler */ private defineGetterPropertyValue( - setterProps: FrameworkDecoratorMetadata[], + prop: FrameworkDecoratorMetadata, instance, getterHandler ) { - if (setterProps && getterHandler) { - for (const prop of setterProps) { - if (prop.propertyName) { - Object.defineProperty(instance, prop.propertyName, { - get: () => getterHandler(prop.key, instance), - configurable: true, // 继承对象有可能会有相同属性,这里需要配置成 true - enumerable: true, - }); - } + if (prop && getterHandler) { + if (prop.propertyName) { + Object.defineProperty(instance, prop.propertyName, { + get: () => getterHandler(prop.key, instance), + configurable: true, // 继承对象有可能会有相同属性,这里需要配置成 true + enumerable: true, + }); } } } diff --git a/packages/core/src/definitions/functionDefinition.ts b/packages/core/src/definitions/functionDefinition.ts index d49794ad7921..5e2116e65b2b 100644 --- a/packages/core/src/definitions/functionDefinition.ts +++ b/packages/core/src/definitions/functionDefinition.ts @@ -8,6 +8,7 @@ import { IObjectCreator, IObjectDefinition, IApplicationContext, + HandlerProp, } from '../interface'; import { ObjectCreator } from './objectCreator'; @@ -51,6 +52,7 @@ export class FunctionDefinition implements IObjectDefinition { properties: IProperties; namespace = ''; asynchronous = true; + handlerProps: HandlerProp[] = []; // 函数工厂创建的对象默认不需要自动装配 protected innerAutowire = false; protected innerScope: ScopeEnum = ScopeEnum.Singleton; diff --git a/packages/core/src/definitions/objectDefinition.ts b/packages/core/src/definitions/objectDefinition.ts index f11c4247209b..9e8274030cc4 100644 --- a/packages/core/src/definitions/objectDefinition.ts +++ b/packages/core/src/definitions/objectDefinition.ts @@ -1,4 +1,4 @@ -import { IObjectCreator, IObjectDefinition } from '../interface'; +import { IObjectCreator, IObjectDefinition, HandlerProp } from '../interface'; import { ScopeEnum, ObjectIdentifier } from '@midwayjs/decorator'; import { ObjectProperties } from './properties'; import { ObjectCreator } from './objectCreator'; @@ -23,6 +23,7 @@ export class ObjectDefinition implements IObjectDefinition { dependsOn: ObjectIdentifier[] = []; properties = new ObjectProperties(); namespace = ''; + handlerProps: HandlerProp[] = []; constructor() { this.creator = new ObjectCreator(this); diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index e7b979fa6ae1..9cc35ab5c8ab 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -67,6 +67,13 @@ export interface IObjectDefinition { getAttr(key: ObjectIdentifier): any; hasAttr(key: ObjectIdentifier): boolean; setAttr(key: ObjectIdentifier, value: any): void; + // 暂存依赖的 key、propertyName + handlerProps: HandlerProp[]; +} + +export interface HandlerProp { + handlerKey: string; + prop: FrameworkDecoratorMetadata; } /** @@ -88,9 +95,15 @@ export interface IObjectDefinitionMetadata { constructorArgs: Array<{ value?: string; args?: any; type: string; } | undefined>; asynchronous: boolean; properties: any[]; - definitionType: 'object' | 'function' + definitionType: 'object' | 'function'; + // 暂存依赖的 key、propertyName + handlerProps: HandlerProp[]; } +export interface FrameworkDecoratorMetadata { + key: string; + propertyName: string; +} export interface IObjectCreator { load(): any; diff --git a/packages/core/test/context/container.test.ts b/packages/core/test/context/container.test.ts index a3eaa7df41d4..0258fcea8d8b 100644 --- a/packages/core/test/context/container.test.ts +++ b/packages/core/test/context/container.test.ts @@ -11,10 +11,11 @@ import { Samurai, Warrior, SubParent, - SubChild + SubChild, + SubCustom } from '../fixtures/class_sample'; import { recursiveGetMetadata } from '../../src/common/reflectTool'; -import { TAGGED_PROP } from '@midwayjs/decorator'; +import { APPLICATION_KEY, CONFIG_KEY, PLUGIN_KEY, TAGGED_PROP } from '@midwayjs/decorator'; import 'reflect-metadata'; import { BMWX1, Car, Electricity, Gas, Tesla, Turbo } from '../fixtures/class_sample_car'; @@ -136,6 +137,39 @@ describe('/test/context/container.test.ts', () => { ]); }); + it('should extends base with decorator be ok', async () => { + const container = new Container(); + container.bind(SubCustom); + + container.registerDataHandler(APPLICATION_KEY, () => { + return {appName: 'hello'}; + }); + container.registerDataHandler(PLUGIN_KEY, (key) => { + if (key === 'hh') { + return {hh: 123}; + } + return {d: 'hello'}; + }); + container.registerDataHandler(CONFIG_KEY, (key) => { + if (key === 'hello') { + return {hello: 'this is hello config'} + } + + if (key === 'tt') { + return 'this is tt config'; + } + + return {}; + }); + + const inst: SubCustom = await container.getAsync('subCustom'); + expect(inst.a).deep.eq({ appName: 'hello'}); + expect(inst.bb).deep.eq({ d: 'hello' }); + expect(inst.hello).deep.eq({hello: 'this is hello config'}); + expect(inst.p).deep.eq({hh: 123}); + expect(inst.tt).eq('this is tt config'); + }); + it('should throw error with class name when injected property error', async () => { const container = new Container(); container.bind('grandson', Grandson as any); diff --git a/packages/core/test/fixtures/class_sample.ts b/packages/core/test/fixtures/class_sample.ts index 155da956f62a..ee4f40b8fbe5 100644 --- a/packages/core/test/fixtures/class_sample.ts +++ b/packages/core/test/fixtures/class_sample.ts @@ -1,4 +1,4 @@ -import {Async, Destroy, Init, Inject, Provide} from '@midwayjs/decorator'; +import {App, Async, Config, Destroy, Init, Inject, Provide, Plugin} from '@midwayjs/decorator'; export interface Warrior { katana1; @@ -89,3 +89,25 @@ export class SubParent { @Inject() subChild: SubChild; } + +export class ParentCustom { + @Config('hello') + hello: any; + + @App() + a: any; + + @Plugin('hh') + p: any; +} +@Provide('subCustom') +export class SubCustom extends ParentCustom { + @Config('tt') + tt: any; + + @App() + a: any; + + @Plugin() + bb: any; +} \ No newline at end of file diff --git a/packages/decorator/src/common/decoratorManager.ts b/packages/decorator/src/common/decoratorManager.ts index 77c91fc48a85..a0aa3ddff4b0 100644 --- a/packages/decorator/src/common/decoratorManager.ts +++ b/packages/decorator/src/common/decoratorManager.ts @@ -31,11 +31,13 @@ export type DecoratorKey = string | symbol; export const PRELOAD_MODULE_KEY = 'INJECTION_PRELOAD_MODULE_KEY'; +export const INJECT_CLASS_KEY_PREFIX = 'INJECTION_CLASS_META_DATA'; + export class DecoratorManager extends Map { /** * the key for meta data store in class */ - injectClassKeyPrefix = 'INJECTION_CLASS_META_DATA'; + injectClassKeyPrefix = INJECT_CLASS_KEY_PREFIX; /** * the key for method meta data store in class */ @@ -61,6 +63,10 @@ export class DecoratorManager extends Map { return decoratorNameKey.toString() + '_CLS'; } + static removeDecoratorClassKeySuffix(decoratorNameKey: DecoratorKey) { + return decoratorNameKey.toString().replace('_CLS', ''); + } + static getDecoratorMethodKey(decoratorNameKey: DecoratorKey) { return decoratorNameKey.toString() + '_METHOD'; }