Skip to content

Commit 6091fc6

Browse files
authored
feat: add aop runtime/dynamic inject runtime for standalone (#149)
<!-- Thank you for your pull request. Please review below requirements. Bug fixes and new features should include tests and possibly benchmarks. Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md 感谢您贡献代码。请确认下列 checklist 的完成情况。 Bug 修复和新功能必须包含测试,必要时请附上性能测试。 Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md --> ##### Checklist <!-- Remove items that do not apply. For completed items, change [ ] to [x]. --> - [x] `npm test` passes - [x] tests and/or benchmarks are included - [ ] documentation is changed or added - [x] commit message follows commit guidelines ##### Affected core subsystem(s) <!-- Provide affected core subsystem(s). --> ##### Description of change <!-- Provide a description of the change below this comment. --> <!-- - any feature? - close https://github.com/eggjs/egg/ISSUE_URL -->
1 parent 77eaf38 commit 6091fc6

20 files changed

Lines changed: 429 additions & 3 deletions

standalone/standalone/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"@eggjs/tegg-lifecycle": "^3.17.0",
4747
"@eggjs/tegg-loader": "^3.17.0",
4848
"@eggjs/tegg-metadata": "^3.17.0",
49-
"@eggjs/tegg-runtime": "^3.17.0"
49+
"@eggjs/tegg-runtime": "^3.17.0",
50+
"@eggjs/tegg-aop-runtime": "^3.17.0"
5051
},
5152
"publishConfig": {
5253
"access": "public"

standalone/standalone/src/ConfigSourceLoadUnitHook.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import {
55
} from '@eggjs/tegg';
66
import { ConfigSourceQualifier, ConfigSourceQualifierAttribute } from './ConfigSource';
77

8+
/**
9+
* Hook for inject moduleConfig.
10+
* Add default qualifier value is current module name.
11+
*/
812
export class ConfigSourceLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
913
async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
1014
const classList = ctx.loader.load();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { LifecycleHook } from '@eggjs/tegg-lifecycle';
2+
import {
3+
EggPrototypeFactory,
4+
LoadUnit,
5+
LoadUnitLifecycleContext,
6+
EggPrototypeCreatorFactory,
7+
} from '@eggjs/tegg-metadata';
8+
import { EggProtoImplClass } from '@eggjs/tegg';
9+
import { EggObjectFactory } from '@eggjs/tegg-dynamic-inject-runtime';
10+
11+
const INNER_CLASS_LIST = [
12+
EggObjectFactory,
13+
];
14+
15+
export class LoadUnitInnerClassHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
16+
async postCreate(_: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
17+
if (loadUnit.type === 'StandaloneLoadUnitType') {
18+
for (const clazz of INNER_CLASS_LIST) {
19+
const protos = await EggPrototypeCreatorFactory.createProto(clazz as EggProtoImplClass, loadUnit);
20+
for (const proto of protos) {
21+
EggPrototypeFactory.instance.registerPrototype(proto, loadUnit);
22+
}
23+
}
24+
}
25+
}
26+
}

standalone/standalone/src/Runner.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
import { ModuleConfigUtil, ModuleReference, RuntimeConfig } from '@eggjs/tegg-common-util';
22
import {
3-
EggPrototype,
3+
EggPrototype, EggPrototypeLifecycleUtil,
44
LoadUnit,
55
LoadUnitFactory,
66
LoadUnitLifecycleUtil,
77
} from '@eggjs/tegg-metadata';
88
import {
99
ContextHandler,
10-
EggContainerFactory, EggContext,
10+
EggContainerFactory, EggContext, EggObjectLifecycleUtil,
1111
LoadUnitInstance,
1212
LoadUnitInstanceFactory,
1313
ModuleLoadUnitInstance,
1414
} from '@eggjs/tegg-runtime';
1515
import { EggProtoImplClass, PrototypeUtil } from '@eggjs/tegg';
1616
import { StandaloneUtil, MainRunner } from '@eggjs/tegg/standalone';
17+
import { CrosscutAdviceFactory } from '@eggjs/tegg/aop';
18+
import { EggObjectAopHook, EggPrototypeCrossCutHook, LoadUnitAopHook } from '@eggjs/tegg-aop-runtime';
19+
1720
import { EggModuleLoader } from './EggModuleLoader';
1821
import { InnerObject, StandaloneLoadUnit, StandaloneLoadUnitType } from './StandaloneLoadUnit';
1922
import { StandaloneContext } from './StandaloneContext';
2023
import { StandaloneContextHandler } from './StandaloneContextHandler';
2124
import { ModuleConfigHolder, ModuleConfigs } from './ModuleConfigs';
2225
import { ConfigSourceQualifierAttribute } from './ConfigSource';
2326
import { ConfigSourceLoadUnitHook } from './ConfigSourceLoadUnitHook';
27+
import { LoadUnitInnerClassHook } from './LoadUnitInnerClassHook';
2428

2529
export interface RunnerOptions {
2630
/**
@@ -40,6 +44,12 @@ export class Runner {
4044
private runnerProto: EggPrototype;
4145
private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook;
4246

47+
private readonly loadUnitInnerClassHook: LoadUnitInnerClassHook;
48+
private readonly crosscutAdviceFactory: CrosscutAdviceFactory;
49+
private readonly loadUnitAopHook: LoadUnitAopHook;
50+
private readonly eggPrototypeCrossCutHook: EggPrototypeCrossCutHook;
51+
private readonly eggObjectAopHook: EggObjectAopHook;
52+
4353
loadUnits: LoadUnit[] = [];
4454
loadUnitInstances: LoadUnitInstance[] = [];
4555
innerObjects: Record<string, InnerObject[]>;
@@ -96,6 +106,20 @@ export class Runner {
96106
this.loadUnitLoader = new EggModuleLoader(this.moduleReferences);
97107
const configSourceEggPrototypeHook = new ConfigSourceLoadUnitHook();
98108
LoadUnitLifecycleUtil.registerLifecycle(configSourceEggPrototypeHook);
109+
110+
this.loadUnitInnerClassHook = new LoadUnitInnerClassHook();
111+
LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitInnerClassHook);
112+
113+
// TODO refactor with egg module
114+
// aop runtime
115+
this.crosscutAdviceFactory = new CrosscutAdviceFactory();
116+
this.loadUnitAopHook = new LoadUnitAopHook(this.crosscutAdviceFactory);
117+
this.eggPrototypeCrossCutHook = new EggPrototypeCrossCutHook(this.crosscutAdviceFactory);
118+
this.eggObjectAopHook = new EggObjectAopHook();
119+
120+
EggPrototypeLifecycleUtil.registerLifecycle(this.eggPrototypeCrossCutHook);
121+
LoadUnitLifecycleUtil.registerLifecycle(this.loadUnitAopHook);
122+
EggObjectLifecycleUtil.registerLifecycle(this.eggObjectAopHook);
99123
}
100124

101125
async init() {
@@ -165,5 +189,19 @@ export class Runner {
165189
if (this.configSourceEggPrototypeHook) {
166190
LoadUnitLifecycleUtil.deleteLifecycle(this.configSourceEggPrototypeHook);
167191
}
192+
193+
if (this.loadUnitInnerClassHook) {
194+
LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitInnerClassHook);
195+
}
196+
197+
if (this.eggPrototypeCrossCutHook) {
198+
EggPrototypeLifecycleUtil.deleteLifecycle(this.eggPrototypeCrossCutHook);
199+
}
200+
if (this.loadUnitAopHook) {
201+
LoadUnitLifecycleUtil.deleteLifecycle(this.loadUnitAopHook);
202+
}
203+
if (this.eggObjectAopHook) {
204+
EggObjectLifecycleUtil.deleteLifecycle(this.eggObjectAopHook);
205+
}
168206
}
169207
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import {
2+
ContextProto,
3+
Inject,
4+
SingletonProto,
5+
} from '@eggjs/tegg';
6+
import {
7+
Advice,
8+
AdviceContext,
9+
Crosscut,
10+
IAdvice,
11+
Pointcut,
12+
PointcutType,
13+
} from '@eggjs/tegg/aop';
14+
import assert from 'assert';
15+
16+
export interface CallTraceMsg {
17+
className: string;
18+
methodName: string;
19+
id: number;
20+
name: string;
21+
result?: string;
22+
adviceParams?: any;
23+
}
24+
25+
@SingletonProto()
26+
export class CallTrace {
27+
msgs: Array<CallTraceMsg> = [];
28+
29+
addMsg(msg: CallTraceMsg) {
30+
this.msgs.push(msg);
31+
}
32+
}
33+
34+
export const pointcutAdviceParams = {
35+
point: Math.random()
36+
.toString(),
37+
cut: Math.random()
38+
.toString(),
39+
};
40+
41+
@Advice()
42+
export class PointcutAdvice implements IAdvice<Hello> {
43+
@Inject()
44+
callTrace: CallTrace;
45+
46+
async beforeCall(ctx: AdviceContext<Hello>): Promise<void> {
47+
assert.ok(ctx.adviceParams);
48+
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
49+
this.callTrace.addMsg({
50+
className: PointcutAdvice.name,
51+
methodName: 'beforeCall',
52+
id: ctx.that.id,
53+
name: ctx.args[0],
54+
adviceParams: ctx.adviceParams,
55+
});
56+
}
57+
58+
async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {
59+
assert.ok(ctx.adviceParams);
60+
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
61+
this.callTrace.addMsg({
62+
className: PointcutAdvice.name,
63+
methodName: 'afterReturn',
64+
id: ctx.that.id,
65+
name: ctx.args[0],
66+
result,
67+
adviceParams: ctx.adviceParams,
68+
});
69+
}
70+
71+
async afterThrow(ctx: AdviceContext<Hello, any>, error: Error): Promise<void> {
72+
assert.ok(ctx.adviceParams);
73+
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
74+
this.callTrace.addMsg({
75+
className: PointcutAdvice.name,
76+
methodName: 'afterThrow',
77+
id: ctx.that.id,
78+
name: ctx.args[0],
79+
result: error.message,
80+
adviceParams: ctx.adviceParams,
81+
});
82+
}
83+
84+
async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {
85+
assert.ok(ctx.adviceParams);
86+
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
87+
this.callTrace.addMsg({
88+
className: PointcutAdvice.name,
89+
methodName: 'afterFinally',
90+
id: ctx.that.id,
91+
name: ctx.args[0],
92+
adviceParams: ctx.adviceParams,
93+
});
94+
}
95+
96+
async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {
97+
assert.ok(ctx.adviceParams);
98+
assert.deepStrictEqual(ctx.adviceParams, pointcutAdviceParams);
99+
ctx.args[0] = `withPointAroundParam(${ctx.args[0]})`;
100+
const result = await next();
101+
return `withPointAroundResult(${result}${JSON.stringify(pointcutAdviceParams)})`;
102+
}
103+
}
104+
105+
@ContextProto()
106+
export class Hello {
107+
id = 233;
108+
109+
@Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })
110+
async hello(name: string) {
111+
return `hello ${name}`;
112+
}
113+
114+
@Pointcut(PointcutAdvice, { adviceParams: pointcutAdviceParams })
115+
async helloWithException(name: string) {
116+
throw new Error(`ops, exception for ${name}`);
117+
}
118+
119+
}
120+
121+
export const crosscutAdviceParams = {
122+
cross: Math.random()
123+
.toString(),
124+
cut: Math.random()
125+
.toString(),
126+
};
127+
128+
@Crosscut({
129+
type: PointcutType.CLASS,
130+
clazz: Hello,
131+
methodName: 'hello',
132+
}, { adviceParams: crosscutAdviceParams })
133+
@Advice()
134+
export class CrosscutAdvice implements IAdvice<Hello, String> {
135+
@Inject()
136+
callTrace: CallTrace;
137+
138+
async beforeCall(ctx: AdviceContext<Hello, {}>): Promise<void> {
139+
assert.ok(ctx.adviceParams);
140+
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
141+
this.callTrace.addMsg({
142+
className: CrosscutAdvice.name,
143+
methodName: 'beforeCall',
144+
id: ctx.that.id,
145+
name: ctx.args[0],
146+
adviceParams: ctx.adviceParams,
147+
});
148+
}
149+
150+
async afterReturn(ctx: AdviceContext<Hello>, result: any): Promise<void> {
151+
assert.ok(ctx.adviceParams);
152+
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
153+
this.callTrace.addMsg({
154+
className: CrosscutAdvice.name,
155+
methodName: 'afterReturn',
156+
id: ctx.that.id,
157+
name: ctx.args[0],
158+
result,
159+
adviceParams: ctx.adviceParams,
160+
});
161+
}
162+
163+
async afterFinally(ctx: AdviceContext<Hello>): Promise<void> {
164+
assert.ok(ctx.adviceParams);
165+
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
166+
this.callTrace.addMsg({
167+
className: CrosscutAdvice.name,
168+
methodName: 'afterFinally',
169+
id: ctx.that.id,
170+
name: ctx.args[0],
171+
adviceParams: ctx.adviceParams,
172+
});
173+
}
174+
175+
async around(ctx: AdviceContext<Hello>, next: () => Promise<any>): Promise<any> {
176+
assert.ok(ctx.adviceParams);
177+
assert.deepStrictEqual(ctx.adviceParams, crosscutAdviceParams);
178+
ctx.args[0] = `withCrosscutAroundParam(${ctx.args[0]})`;
179+
const result = await next();
180+
return `withCrossAroundResult(${result}${JSON.stringify(ctx.adviceParams)})`;
181+
}
182+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ContextProto, Inject } from '@eggjs/tegg';
2+
import { Runner, MainRunner } from '@eggjs/tegg/standalone';
3+
import { Hello } from './Hello';
4+
5+
6+
@Runner()
7+
@ContextProto()
8+
export class Foo implements MainRunner<string> {
9+
@Inject()
10+
hello: Hello;
11+
12+
async main(): Promise<string> {
13+
return await this.hello.hello('aop');
14+
}
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "aop-module",
3+
"eggModule": {
4+
"name": "aopModule"
5+
}
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export abstract class AbstractContextHello {
2+
abstract hello(): string;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export abstract class AbstractSingletonHello {
2+
abstract hello(): string;
3+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum ContextHelloType {
2+
FOO = 'FOO',
3+
BAR = 'BAR',
4+
}
5+
6+
export enum SingletonHelloType {
7+
FOO = 'FOO',
8+
BAR = 'BAR',
9+
}

0 commit comments

Comments
 (0)