Skip to content

Commit

Permalink
fix: property decorator and class decorator extends (#845)
Browse files Browse the repository at this point in the history
* fix: 修复自定义装饰器无法继承问题

* refactor: 重构 handler 获取机制,bind 时预先存储到 definition 中

* fix impl

* fix lint

* add test case
  • Loading branch information
kurten authored Mar 1, 2021
1 parent d215a3f commit 8d0227d
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ package-lock.json
.nodejs-cache
.tmp
packages/midway/_package.json
midway_benchmark_app
8 changes: 8 additions & 0 deletions packages/core/src/common/reflectTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 43 additions & 11 deletions packages/core/src/context/midwayContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
ASPECT_KEY,
listPreloadModule,
isProxy,
INJECT_CLASS_KEY_PREFIX,
DecoratorManager,
ResolveFilter,
isRegExp,
} from '@midwayjs/decorator';
Expand All @@ -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';
Expand Down Expand Up @@ -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,
});
}
}
}
}
}
}
Expand Down Expand Up @@ -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);
}
Expand Down
53 changes: 24 additions & 29 deletions packages/core/src/context/resolverHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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)
);
}
}
}
/**
Expand All @@ -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,
});
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/definitions/functionDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IObjectCreator,
IObjectDefinition,
IApplicationContext,
HandlerProp,
} from '../interface';
import { ObjectCreator } from './objectCreator';

Expand Down Expand Up @@ -51,6 +52,7 @@ export class FunctionDefinition implements IObjectDefinition {
properties: IProperties;
namespace = '';
asynchronous = true;
handlerProps: HandlerProp[] = [];
// 函数工厂创建的对象默认不需要自动装配
protected innerAutowire = false;
protected innerScope: ScopeEnum = ScopeEnum.Singleton;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/definitions/objectDefinition.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -23,6 +23,7 @@ export class ObjectDefinition implements IObjectDefinition {
dependsOn: ObjectIdentifier[] = [];
properties = new ObjectProperties();
namespace = '';
handlerProps: HandlerProp[] = [];

constructor() {
this.creator = new ObjectCreator(this);
Expand Down
15 changes: 14 additions & 1 deletion packages/core/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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;
Expand Down
38 changes: 36 additions & 2 deletions packages/core/test/context/container.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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', Grandson as any);
Expand Down
24 changes: 23 additions & 1 deletion packages/core/test/fixtures/class_sample.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
}
8 changes: 7 additions & 1 deletion packages/decorator/src/common/decoratorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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';
}
Expand Down

0 comments on commit 8d0227d

Please sign in to comment.