在VonaJS框架中,AOP编程包括三方面:控制器切面、内部切面和外部切面。
控制器切面: 为 Controller 方法切入逻辑,包括:Middleware、Guard、Interceptor、Pipe和Filter内部切面: 在 Class 内部,为任何 Class 的任何方法切入逻辑,包括:AOP Method和魔术方法外部切面: 在不改变 Class 源码的前提下,从外部为任何 Class 的任何方法切入逻辑
VonaJS中的外部切面,可以类比于Spring Boot中的AOP切面和AOP织入概念。VonaJS的外部切面不需要什么前置通知、后置通知、异常通知和环绕通知,只需提供一个同名方法就可以了。之所以可以这么简洁,是因为使用了洋葱圈模型。
此外,VonaJS的外部切面支持完整的类型推断与智能代码提示,开发体感比Spring Boot优雅太多。
下面,我们就来考察一下VonaJS的外部切面到底是个什么样?为什么可以成为AOP编程的🚀大杀器🔪
创建目标Class
可以针对任何 Class 实现外部切面。下面,以 Service 为例,在模块 demo-student 中创建一个 Service test,代码如下:
typescript
@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);
}
}
创建外部切面
接下来,创建一个外部切面log,为 Class ServiceTest的属性和方法分别提供扩展逻辑
1. Cli命令
bash
$ vona :create:bean aop log --module=demo-student
2. 菜单命令
bash
右键菜单 - [模块路径]: Vona Aspect/Aop
AOP定义
typescript
import { BeanAopBase } from 'vona';
import { Aop } from 'vona-module-a-aspect';
@Aop({ match: 'demo-student.service.test' })
export class AopLog extends BeanAopBase {}
@Aop: 此装饰器用于实现外部切面match: 用于将 ClassAopLog与 ClassServiceTest关联,ServiceTest的 beanFullName 是demo-student.service.test
| 名称 | 类型 | 说明 |
|---|---|---|
| match | string|regexp|(string|regexp)[] | 针对哪些 Class 启用 |
切面:同步方法
为ServiceTest#actionSync输出运行时长
在 VSCode 编辑器中,输入代码片段aopactionsync,自动生成代码骨架:
typescript
action: AopAction<ClassSome, 'action'> = (_args, next, _receiver) => {
return next();
};
调整代码,然后添加 log 逻辑
typescript
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输出运行时长
在 VSCode 编辑器中,输入代码片段aopaction,自动生成代码骨架:
typescript
action: AopAction<ClassSome, 'action'> = async (_args, next, _receiver) => {
return await next();
};
调整代码,然后添加 log 逻辑
typescript
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输出运行时长
在 VSCode 编辑器中,输入代码片段aopgetter,自动生成代码骨架:
typescript
protected __get_xxx__: AopActionGetter<ClassSome, 'xxx'> = function (next, _receiver) {
const value = next();
return value;
};
调整代码,然后添加 log 逻辑
typescript
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__: 对应ServiceTest的 getter 方法get name
切面:setter
为ServiceTest#set name输出运行时长
在 VSCode 编辑器中,输入代码片段aopsetter,自动生成代码骨架:
typescript
protected __set_xxx__: AopActionSetter<ClassSome, 'xxx'> = function (value, next, _receiver) {
return next(value);
}
调整代码,然后添加 log 逻辑
typescript
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__: 对应ServiceTest的 setter 方法set name
切面:__init__
为ServiceTest#__init__输出运行时长
在 VSCode 编辑器中,输入代码片段aopinit,自动生成代码骨架:
typescript
protected __init__: AopActionInit<ClassSome> = (_args, next, _receiver) => {
next();
};
调整代码,然后添加 log 逻辑
typescript
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__输出运行时长
在 VSCode 编辑器中,输入代码片段aopdispose,自动生成代码骨架:
typescript
protected __dispose__: AopActionDispose<ClassSome> = async (_args, next, _receiver) => {
await next();
};
调整代码,然后添加 log 逻辑
typescript
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扩展魔术方法
- 参见: 魔术方法
在 VSCode 编辑器中,输入代码片段aopget,自动生成代码骨架:
typescript
protected __get__: AopActionGet<ClassSome> = (_prop, next, _receiver) => {
const value = next();
return value;
};
调整代码,然后添加自定义字段red
typescript
protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => {
if (prop === 'red') return '#FF0000';
const value = next();
return value;
};
__get__: 约定的魔术方法名称
通过接口类型合并的机制为颜色提供类型定义
typescript
declare module 'vona-module-demo-student' {
export interface ServiceTest {
red: string;
}
}
切面:__set__
为ServiceTest扩展魔术方法
- 参见: 魔术方法
在 VSCode 编辑器中,输入代码片段aopset,自动生成代码骨架:
typescript
protected __set__: AopActionSet<ClassSome> = (_prop, value, next, _receiver) => {
return next(value);
};
调整代码,为自定义字段red设置值
typescript
private _colorRed: string | undefined;
protected __set__: AopActionSet<ServiceTest> = (prop, value, next, _receiver) => {
if (prop === 'red') {
this._colorRed = value;
return true;
}
return next(value);
};
__set__: 约定的魔术方法名称- 如果为
prop设置了值,返回true,否则调用next方法
然后调整__get__的逻辑:
diff
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的任何方法扩展逻辑
在 VSCode 编辑器中,输入代码片段aopmethod,自动生成代码骨架:
typescript
protected __method__: AopActionMethod<ClassSome> = (_method, _args, next, _receiver) => {
return next();
};
调整代码,然后为方法actionSync和actionAsync添加 log 逻辑
typescript
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__: 约定的魔术方法名称res?.then: 判断返回值是否是 Promise 对象,进行不同处理,从而兼容同步方法和异步方法
AOP顺序
针对同一个目标 Class,可以关联多个 AOP。所以,VonaJS 提供了两个参数,用于控制 AOP 的执行顺序
1. dependencies
比如,还有一个 AOP demo-student:log3,我们希望执行顺序如下:demo-student:log3 > Current
diff
@Aop({
match: 'demo-student.service.test',
+ dependencies: 'demo-student:log3',
})
class AopLog {}
2. dependents
dependents的顺序刚好与dependencies相反,我们希望执行顺序如下:Current > demo-student:log3
diff
@Aop({
match: 'demo-student.service.test',
+ dependents: 'demo-student:log3',
})
class AopLog {}
AOP启用/禁用
可以控制 AOP 的启用/禁用
1. Enable
src/backend/config/config/config.ts
diff
// onions
config.onions = {
aop: {
'demo-student:log': {
+ enable: false,
},
},
};
2. Meta
可以让 AOP 在指定的运行环境生效
| 名称 | 类型 | 说明 |
|---|---|---|
| flavor | string|string[] | 参见: 运行环境与Flavor |
| mode | string|string[] | 参见: 运行环境与Flavor |
- 举例
diff
@Aop({
+ meta: {
+ flavor: 'normal',
+ mode: 'dev',
+ },
})
class AopLog {}
查看当前生效的AOP清单
可以直接在目标 Class action 中输出当前生效的 AOP 清单
diff
class ServiceTest {
protected async __dispose__() {
+ this.bean.onion.aop.inspect();
this._name = '';
}
this.bean.onion: 取得全局 Service 实例onion.aop: 取得与 AOP 相关的 Service 实例.inspect: 输出当前生效的 AOP 清单
当方法被执行时,会自动在控制台输出当前生效的 AOP 清单,效果如下: