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 清单,效果如下:

资源

相关推荐
时光不负努力1 小时前
编程常用模式集合
前端·javascript·typescript
时光不负努力1 小时前
ts+vue3开发规范
vue.js·typescript
Gogo11212 小时前
构建高性能 Node.js 集中式日志体系 (下篇):Pino + PM2 + OpenSearch 代码落地实战
node.js
小岛前端2 小时前
Node.js 宣布重大调整,运行十年的规则要改了!
前端·node.js
时光不负努力2 小时前
typescript常用的dom 元素类型
前端·typescript
时光不负努力2 小时前
TS 常用工具类型
前端·javascript·typescript
前端付豪3 小时前
Nest 项目小实践之前端注册登陆
前端·node.js·nestjs
codingWhat20 小时前
整理「祖传」代码,就是在开发脚手架?
前端·javascript·node.js
Wect20 小时前
LeetCode 210. 课程表 II 题解:Kahn算法+DFS 双解法精讲
前端·算法·typescript
ServBay20 小时前
Node.js、Bun 与 Deno,2026 年后端运行时选择指南
node.js·deno·bun