Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/aop-runtime/src/LoadUnitAopHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class LoadUnitAopHook implements LifecycleHook<LoadUnitLifecycleContext,
crosscutAdviceFactory: this.crosscutAdviceFactory,
});
const aspectList = builder.build();
AspectInfoUtil.setAspectList(aspectList, clazz);
for (const aspect of aspectList) {
AspectInfoUtil.setAspectList(aspectList, clazz);
for (const advice of aspect.adviceList) {
const adviceProto = PrototypeUtil.getClazzProto(advice.clazz);
if (!adviceProto) {
Expand Down
9 changes: 9 additions & 0 deletions plugin/aop/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Application } from 'egg';
import { CrosscutAdviceFactory } from '@eggjs/tegg/aop';
import { EggObjectAopHook, EggPrototypeCrossCutHook, LoadUnitAopHook } from '@eggjs/tegg-aop-runtime';
import { AopContextHook } from './lib/AopContextHook';

export default class AopAppHook {
private readonly app: Application;
Expand All @@ -9,6 +10,7 @@ export default class AopAppHook {
private readonly loadUnitAopHook: LoadUnitAopHook;
private readonly eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;
private readonly eggObjectAopHook: EggObjectAopHook;
private aopContextHook: AopContextHook;

constructor(app) {
this.app = app;
Expand All @@ -24,9 +26,16 @@ export default class AopAppHook {
this.app.eggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook);
}

async didLoad() {
await this.app.moduleHandler.ready();
this.aopContextHook = new AopContextHook(this.app.moduleHandler);
this.app.eggContextLifecycleUtil.registerLifecycle(this.aopContextHook);
}

beforeClose() {
this.app.eggPrototypeLifecycleUtil.deleteLifecycle(this.eggPrototypeCrossCutHook);
this.app.loadUnitLifecycleUtil.deleteLifecycle(this.loadUnitAopHook);
this.app.eggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook);
this.app.eggContextLifecycleUtil.deleteLifecycle(this.aopContextHook);
}
}
57 changes: 57 additions & 0 deletions plugin/aop/lib/AopContextHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Application } from 'egg';
import { EggProtoImplClass, LifecycleHook, ObjectInitType, PrototypeUtil } from '@eggjs/tegg';
import { EggContext, EggContextLifecycleContext } from '@eggjs/tegg-runtime';
import { AspectInfoUtil } from '@eggjs/aop-decorator';
import { EggPrototype, TeggError } from '@eggjs/tegg-metadata';
import { ROOT_PROTO } from '@eggjs/egg-module-common';

export interface EggPrototypeWithClazz extends EggPrototype {
clazz?: EggProtoImplClass;
}

export interface ProtoToCreate {
name: string;
proto: EggPrototype;
}

export class AopContextHook implements LifecycleHook<EggContextLifecycleContext, EggContext> {
private readonly moduleHandler: Application['moduleHandler'];
private requestProtoList: Array<ProtoToCreate> = [];

constructor(moduleHandler: Application['moduleHandler']) {
this.moduleHandler = moduleHandler;
for (const loadUnitInstance of this.moduleHandler.loadUnitInstances) {
const iterator = loadUnitInstance.loadUnit.iterateEggPrototype();
for (const proto of iterator) {
const protoWithClazz = proto as EggPrototypeWithClazz;
const clazz = protoWithClazz.clazz;
if (!clazz) continue;
const aspects = AspectInfoUtil.getAspectList(clazz);
for (const aspect of aspects) {
for (const advice of aspect.adviceList) {
const adviceProto = PrototypeUtil.getClazzProto(advice.clazz) as EggPrototype | undefined;
if (!adviceProto) {
throw TeggError.create(`Aop Advice(${advice.clazz.name}) not found in loadUnits`, 'advice_not_found');
}
if (adviceProto.initType === ObjectInitType.CONTEXT) {
this.requestProtoList.push({
name: advice.name,
proto: adviceProto,
});
}
}
}
}
}
}

async preCreate(_, ctx: EggContext): Promise<void> {
// compatible with egg controller
// add context aspect to ctx
if (!ctx.get(ROOT_PROTO)) {
for (const proto of this.requestProtoList) {
ctx.addProtoToCreate(proto.name, proto.proto);
}
}
}
}
10 changes: 10 additions & 0 deletions plugin/aop/test/aop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,14 @@ describe('test/aop.test.ts', () => {
msg: 'withCrossAroundResult(withPointAroundResult(hello withPointAroundParam(withCrosscutAroundParam(foo))))',
});
});

it('module aop should work', async () => {
app.mockCsrf();
const res = await app.httpRequest()
.get('/singletonAop')
.expect(200);
assert.deepStrictEqual(res.body, {
msg: 'withContextPointAroundResult(hello withContextPointAroundParam(foo))',
});
});
});
7 changes: 7 additions & 0 deletions plugin/aop/test/fixtures/apps/aop-app/app/controller/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ export default class App extends Controller {
this.ctx.status = 200;
this.ctx.body = { msg };
}

async contextAdviceWithSingleton() {
const hello: Hello = await (this.app.module as any).aopModule.singletonHello;
const msg = await hello.hello('foo');
this.ctx.status = 200;
this.ctx.body = { msg };
}
}
1 change: 1 addition & 0 deletions plugin/aop/test/fixtures/apps/aop-app/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import { Application } from 'egg';

module.exports = (app: Application) => {
app.router.get('/aop', app.controller.app.aop);
app.router.get('/singletonAop', app.controller.app.contextAdviceWithSingleton);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';
import { AccessLevel, ContextProto, Inject, SingletonProto } from '@eggjs/tegg';
import { Advice, AdviceContext, Crosscut, IAdvice, Pointcut, PointcutType } from '@eggjs/tegg/aop';
import { EggLogger } from 'egg';

Expand Down Expand Up @@ -43,3 +43,32 @@ export class CrosscutAdvice implements IAdvice<Hello> {
return `withCrossAroundResult(${result})`;
}
}


@Advice()
export class ContextPointcutAdvice implements IAdvice<SingletonHello> {
async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {
ctx.args[0] = `withContextPointAroundParam(${ctx.args[0]})`;
const result = await next();
return `withContextPointAroundResult(${result})`;
}
}

@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class SingletonHello {
id = 233;

@Inject()
logger: EggLogger;

@Pointcut(ContextPointcutAdvice)
async hello(name: string) {
return `hello ${name}`;
}

async helloEggObjectAop() {
this.logger.info('foo');
}
}