VonaJS AOP编程,外部切面如何?

摘要:VonaJS中的外部切面,可以类比于Spring Boot中的AOP切面和AOP织入概念。VonaJS的外部切面不需要什么前置通知、后置通知、异常通知和环绕通知,只需提供一个同名方法就可以了。之所以可以这么简洁,是因为使用了洋葱圈模型。
在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,代码如下: @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命令 $ 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: 此装饰器用于实现外部切面 match: 用于将 Class AopLog与 Class ServiceTest关联,ServiceTest的 beanFullName 是demo-student.service.test 名称 类型 说明 match string|regexp|(string|regexp)[] 针对哪些 Class 启用 切面:同步方法 为ServiceTest#actionSync输出运行时长 在 VSCode 编辑器中,输入代码片段aopactionsync,自动生成代码骨架: action: AopAction<ClassSome, 'action'> = (_args, next, _receiver) => { return next(); }; 调整代码,然后添加 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输出运行时长 在 VSCode 编辑器中,输入代码片段aopaction,自动生成代码骨架: action: AopAction<ClassSome, 'action'> = async (_args, next, _receiver) => { return await next(); }; 调整代码,然后添加 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输出运行时长 在 VSCode 编辑器中,输入代码片段aopgetter,自动生成代码骨架: protected __get_xxx__: AopActionGetter<ClassSome, 'xxx'> = function (next, _receiver) { const value = next(); return value; }; 调整代码,然后添加 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__: 对应ServiceTest的 getter 方法get name 切面:setter 为ServiceTest#set name输出运行时长 在 VSCode 编辑器中,输入代码片段aopsetter,自动生成代码骨架: protected __set_xxx__: AopActionSetter<ClassSome, 'xxx'> = function (value, next, _receiver) { return next(value); } 调整代码,然后添加 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__: 对应ServiceTest的 setter 方法set name 切面:__init__ 为ServiceTest#__init__输出运行时长 在 VSCode 编辑器中,输入代码片段aopinit,自动生成代码骨架: protected __init__: AopActionInit<ClassSome> = (_args, next, _receiver) => { next(); }; 调整代码,然后添加 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__输出运行时长 在 VSCode 编辑器中,输入代码片段aopdispose,自动生成代码骨架: protected __dispose__: AopActionDispose<ClassSome> = async (_args, next, _receiver) => { await next(); }; 调整代码,然后添加 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扩展魔术方法 参见: 魔术方法 在 VSCode 编辑器中,输入代码片段aopget,自动生成代码骨架: protected __get__: AopActionGet<ClassSome> = (_prop, next, _receiver) => { const value = next(); return value; }; 调整代码,然后添加自定义字段red protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => { if (prop === 'red') return '#FF0000'; const value = next(); return value; }; __get__: 约定的魔术方法名称 通过接口类型合并的机制为颜色提供类型定义 declare module 'vona-module-demo-student' { export interface ServiceTest { red: string; } } 切面:__set__ 为ServiceTest扩展魔术方法 参见: 魔术方法 在 VSCode 编辑器中,输入代码片段aopset,自动生成代码骨架: protected __set__: AopActionSet<ClassSome> = (_prop, value, next, _receiver) => { return next(value); }; 调整代码,为自定义字段red设置值 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__的逻辑: 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,自动生成代码骨架: protected __method__: AopActionMethod<ClassSome> = (_method, _args, next, _receiver) => { return next(); }; 调整代码,然后为方法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__: 约定的魔术方法名称 res?.then: 判断返回值是否是 Promise 对象,进行不同处理,从而兼容同步方法和异步方法 AOP顺序 针对同一个目标 Class,可以关联多个 AOP。所以,VonaJS 提供了两个参数,用于控制 AOP 的执行顺序 1. dependencies 比如,还有一个 AOP demo-student:log3,我们希望执行顺序如下:demo-student:log3 > Current @Aop({ match: 'demo-student.service.test', + dependencies: 'demo-student:log3', }) class AopLog {} 2. dependents dependents的顺序刚好与dependencies相反,我们希望执行顺序如下: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 在指定的运行环境生效 名称 类型 说明 flavor string|string[] 参见: 运行环境与Flavor mode string|string[] 参见: 运行环境与Flavor 举例 @Aop({ + meta: { + flavor: 'normal', + mode: 'dev', + }, }) class AopLog {} 查看当前生效的AOP清单 可以直接在目标 Class action 中输出当前生效的 AOP 清单 class ServiceTest { protected async __dispose__() { + this.bean.onion.aop.inspect(); this._name = ''; } this.bean.onion: 取得全局 Service 实例 onion .aop: 取得与 AOP 相关的 Service 实例 .inspect: 输出当前生效的 AOP 清单 当方法被执行时,会自动在控制台输出当前生效的 AOP 清单,效果如下: 资源 Github:https://github.com/vonajs/vona 文档:https://vona.js.org/