VonaJS AOP編程大殺器:外部切面
在VonaJS框架中,AOP編程包括三方面:控制器切面、內(nèi)部切面和外部切面。
控制器切面: 為 Controller 方法切入邏輯,包括:Middleware、Guard、Interceptor、Pipe和Filter內(nèi)部切面: 在 Class 內(nèi)部,為任何 Class 的任何方法切入邏輯,包括:AOP Method和魔術(shù)方法外部切面: 在不改變 Class 源碼的前提下,從外部為任何 Class 的任何方法切入邏輯
VonaJS中的外部切面,可以類比于Spring Boot中的AOP切面和AOP織入概念。VonaJS的外部切面不需要什么前置通知、后置通知、異常通知和環(huán)繞通知,只需提供一個(gè)同名方法就可以了。之所以可以這么簡(jiǎn)潔,是因?yàn)槭褂昧搜笫[圈模型。
此外,VonaJS的外部切面支持完整的類型推斷與智能代碼提示,開(kāi)發(fā)體感比Spring Boot優(yōu)雅太多。
下面,我們就來(lái)考察一下VonaJS的外部切面到底是個(gè)什么樣?為什么可以成為AOP編程的??大殺器??
創(chuàng)建目標(biāo)Class
可以針對(duì)任何 Class 實(shí)現(xiàn)外部切面。下面,以 Service 為例,在模塊 demo-student 中創(chuàng)建一個(gè) Service test,代碼如下:
@Service()
export class ServiceTest extends BeanBase {
private _name: string;
protected __init__() {
this._name = '';
}
protected async __dispose__() {
this._name = '';
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
actionSync(a: number, b: number) {
return a + b;
}
async actionAsync(a: number, b: number) {
return Promise.resolve(a + b);
}
}
創(chuàng)建外部切面
接下來(lái),創(chuàng)建一個(gè)外部切面log,為 Class ServiceTest的屬性和方法分別提供擴(kuò)展邏輯
1. Cli命令
$ vona :create:bean aop log --module=demo-student
2. 菜單命令
右鍵菜單 - [模塊路徑]: Vona Aspect/Aop
AOP定義
import { BeanAopBase } from 'vona';
import { Aop } from 'vona-module-a-aspect';
@Aop({ match: 'demo-student.service.test' })
export class AopLog extends BeanAopBase {}
@Aop: 此裝飾器用于實(shí)現(xiàn)外部切面match: 用于將 ClassAopLog與 ClassServiceTest關(guān)聯(lián),ServiceTest的 beanFullName 是demo-student.service.test
| 名稱 | 類型 | 說(shuō)明 |
|---|---|---|
| match | string|regexp|(string|regexp)[] | 針對(duì)哪些 Class 啟用 |
切面:同步方法
為ServiceTest#actionSync輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopactionsync,自動(dòng)生成代碼骨架:
action: AopAction<ClassSome, 'action'> = (_args, next, _receiver) => {
return next();
};
調(diào)整代碼,然后添加 log 邏輯
actionSync: AopAction<ServiceTest, 'actionSync'> = (_args, next, _receiver) => {
const timeBegin = Date.now();
const res = next();
const timeEnd = Date.now();
console.log('actionSync: ', timeEnd - timeBegin);
return res;
};
actionSync: 提供與ServiceTest同名的方法actionSync
切面:異步方法
為ServiceTest#actionAsync輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopaction,自動(dòng)生成代碼骨架:
action: AopAction<ClassSome, 'action'> = async (_args, next, _receiver) => {
return await next();
};
調(diào)整代碼,然后添加 log 邏輯
actionAsync: AopAction<ServiceTest, 'actionAsync'> = async (_args, next, _receiver) => {
const timeBegin = Date.now();
const res = await next();
const timeEnd = Date.now();
console.log('actionAsync: ', timeEnd - timeBegin);
return res;
};
actionAsync: 提供與ServiceTest同名的方法actionAsync
切面:getter
為ServiceTest#get name輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopgetter,自動(dòng)生成代碼骨架:
protected __get_xxx__: AopActionGetter<ClassSome, 'xxx'> = function (next, _receiver) {
const value = next();
return value;
};
調(diào)整代碼,然后添加 log 邏輯
protected __get_name__: AopActionGetter<ServiceTest, 'name'> = function (next, _receiver) {
const timeBegin = Date.now();
const value = next();
const timeEnd = Date.now();
console.log('get name: ', timeEnd - timeBegin);
return value;
};
__get_name__: 對(duì)應(yīng)ServiceTest的 getter 方法get name
切面:setter
為ServiceTest#set name輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopsetter,自動(dòng)生成代碼骨架:
protected __set_xxx__: AopActionSetter<ClassSome, 'xxx'> = function (value, next, _receiver) {
return next(value);
}
調(diào)整代碼,然后添加 log 邏輯
protected __set_name__: AopActionSetter<ServiceTest, 'name'> = function (value, next, _receiver) {
const timeBegin = Date.now();
const res = next(value);
const timeEnd = Date.now();
console.log('set name: ', timeEnd - timeBegin);
return res;
};
__set_name__: 對(duì)應(yīng)ServiceTest的 setter 方法set name
切面:__init__
為ServiceTest#__init__輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopinit,自動(dòng)生成代碼骨架:
protected __init__: AopActionInit<ClassSome> = (_args, next, _receiver) => {
next();
};
調(diào)整代碼,然后添加 log 邏輯
protected __init__: AopActionInit<ServiceTest> = (_args, next, _receiver) => {
const timeBegin = Date.now();
next();
const timeEnd = Date.now();
console.log('__init__: ', timeEnd - timeBegin);
};
__init__: 提供與ServiceTest同名的方法__init__
切面:__dispose__
為ServiceTest#__dispose__輸出運(yùn)行時(shí)長(zhǎng)
在 VSCode 編輯器中,輸入代碼片段aopdispose,自動(dòng)生成代碼骨架:
protected __dispose__: AopActionDispose<ClassSome> = async (_args, next, _receiver) => {
await next();
};
調(diào)整代碼,然后添加 log 邏輯
protected __dispose__: AopActionDispose<ServiceTest> = async (_args, next, _receiver) => {
const timeBegin = Date.now();
await next();
const timeEnd = Date.now();
console.log('__dispose__: ', timeEnd - timeBegin);
};
__dispose__: 提供與ServiceTest同名的方法__dispose__
切面:__get__
為ServiceTest擴(kuò)展魔術(shù)方法
- 參見(jiàn): 魔術(shù)方法
在 VSCode 編輯器中,輸入代碼片段aopget,自動(dòng)生成代碼骨架:
protected __get__: AopActionGet<ClassSome> = (_prop, next, _receiver) => {
const value = next();
return value;
};
調(diào)整代碼,然后添加自定義字段red
protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => {
if (prop === 'red') return '#FF0000';
const value = next();
return value;
};
__get__: 約定的魔術(shù)方法名稱
通過(guò)接口類型合并的機(jī)制為顏色提供類型定義
declare module 'vona-module-demo-student' {
export interface ServiceTest {
red: string;
}
}
切面:__set__
為ServiceTest擴(kuò)展魔術(shù)方法
- 參見(jiàn): 魔術(shù)方法
在 VSCode 編輯器中,輸入代碼片段aopset,自動(dòng)生成代碼骨架:
protected __set__: AopActionSet<ClassSome> = (_prop, value, next, _receiver) => {
return next(value);
};
調(diào)整代碼,為自定義字段red設(shè)置值
private _colorRed: string | undefined;
protected __set__: AopActionSet<ServiceTest> = (prop, value, next, _receiver) => {
if (prop === 'red') {
this._colorRed = value;
return true;
}
return next(value);
};
__set__: 約定的魔術(shù)方法名稱- 如果為
prop設(shè)置了值,返回true,否則調(diào)用next方法
然后調(diào)整__get__的邏輯:
protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => {
- if (prop === 'red') return '#FF0000';
+ if (prop === 'red') return this._colorRed;
const value = next();
return value;
}
切面:__method__
為ServiceTest的任何方法擴(kuò)展邏輯
在 VSCode 編輯器中,輸入代碼片段aopmethod,自動(dòng)生成代碼骨架:
protected __method__: AopActionMethod<ClassSome> = (_method, _args, next, _receiver) => {
return next();
};
調(diào)整代碼,然后為方法actionSync和actionAsync添加 log 邏輯
protected __method__: AopActionMethod<ServiceTest> = (method, _args, next, _receiver) => {
if (method !== 'actionSync' && method !== 'actionAsync') {
return next();
}
const timeBegin = Date.now();
function done(res) {
const timeEnd = Date.now();
console.log(`method ${method}: `, timeEnd - timeBegin);
return res;
}
const res = next();
if (res?.then) {
return res.then((res: any) => {
return done(res);
});
}
return done(res);
};
__method__: 約定的魔術(shù)方法名稱res?.then: 判斷返回值是否是 Promise 對(duì)象,進(jìn)行不同處理,從而兼容同步方法和異步方法
AOP順序
針對(duì)同一個(gè)目標(biāo) Class,可以關(guān)聯(lián)多個(gè) AOP。所以,VonaJS 提供了兩個(gè)參數(shù),用于控制 AOP 的執(zhí)行順序
1. dependencies
比如,還有一個(gè) AOP demo-student:log3,我們希望執(zhí)行順序如下:demo-student:log3 > Current
@Aop({
match: 'demo-student.service.test',
+ dependencies: 'demo-student:log3',
})
class AopLog {}
2. dependents
dependents的順序剛好與dependencies相反,我們希望執(zhí)行順序如下:Current > demo-student:log3
@Aop({
match: 'demo-student.service.test',
+ dependents: 'demo-student:log3',
})
class AopLog {}
AOP啟用/禁用
可以控制 AOP 的啟用/禁用
1. Enable
src/backend/config/config/config.ts
// onions
config.onions = {
aop: {
'demo-student:log': {
+ enable: false,
},
},
};
2. Meta
可以讓 AOP 在指定的運(yùn)行環(huán)境生效
| 名稱 | 類型 | 說(shuō)明 |
|---|---|---|
| flavor | string|string[] | 參見(jiàn): 運(yùn)行環(huán)境與Flavor |
| mode | string|string[] | 參見(jiàn): 運(yùn)行環(huán)境與Flavor |
- 舉例
@Aop({
+ meta: {
+ flavor: 'normal',
+ mode: 'dev',
+ },
})
class AopLog {}
查看當(dāng)前生效的AOP清單
可以直接在目標(biāo) Class action 中輸出當(dāng)前生效的 AOP 清單
class ServiceTest {
protected async __dispose__() {
+ this.bean.onion.aop.inspect();
this._name = '';
}
this.bean.onion: 取得全局 Service 實(shí)例onion.aop: 取得與 AOP 相關(guān)的 Service 實(shí)例.inspect: 輸出當(dāng)前生效的 AOP 清單
當(dāng)方法被執(zhí)行時(shí),會(huì)自動(dòng)在控制臺(tái)輸出當(dāng)前生效的 AOP 清單,效果如下:


VonaJS中的外部切面,可以類比于Spring Boot中的AOP切面和AOP織入概念。VonaJS的外部切面不需要什么前置通知、后置通知、異常通知和環(huán)繞通知,只需提供一個(gè)同名方法就可以了。之所以可以這么簡(jiǎn)潔,是因?yàn)槭褂昧搜笫[圈模型。
浙公網(wǎng)安備 33010602011771號(hào)