VonaJS AOP编程🚀大杀器🔪:外部切面

在VonaJS框架中,AOP编程包括三方面:控制器切面内部切面外部切面

  1. 控制器切面: 为 Controller 方法切入逻辑,包括:Middleware、Guard、Interceptor、Pipe和Filter
  2. 内部切面: 在 Class 内部,为任何 Class 的任何方法切入逻辑,包括:AOP Method和魔术方法
  3. 外部切面: 在不改变 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: 用于将 Class AopLog与 Class ServiceTest关联,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();
};

调整代码,然后为方法actionSyncactionAsync添加 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 清单,效果如下:

资源

相关推荐
污斑兔3 小时前
技术随笔:Node.js ESM 中巧用 `-r dotenv/config` 解决环境变量异步加载问题
开发语言·r语言·node.js
濮水大叔4 小时前
VonaJS AOP编程大杀器:外部切面
typescript·nodejs·nestjs
Moment4 小时前
Next.js 16 新特性:如何启用 MCP 与 AI 助手协作 🤖🤖🤖
前端·javascript·node.js
还是大剑师兰特16 小时前
TypeScript 面试题及详细答案 100题 (91-100)-- 工程实践与框架集成
前端·javascript·typescript·1024程序员节
fruge1 天前
TypeScript 基础类型与接口详解
javascript·ubuntu·typescript
hweiyu002 天前
Node.js+Koa2+MySQL 打造前后端分离项目(视频教程)
数据库·mysql·node.js
小奶包他干奶奶2 天前
如何使用vscode和express开发node.js
前端·node.js
qyhua2 天前
从零部署自维护版 Uptime Kuma:Node.js + PM2 + Nginx 全链路实战指南
运维·nginx·node.js
海鸥两三2 天前
Uni-App(Vue3 + TypeScript)项目结构详解 ------ 以 Lighting-UniApp 为例,提供源代码
vue.js·typescript·uni-app·1024程序员节