Skip to content

External Aspect ​

Applies logic to any method of any class from the outside without changing the class source code.

Create Target Class ​

External aspect can be implemented for any class. Below, taking Service as an example, create a Service test in the module demo-student with the following code:

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);
  }
}

Create External Aspect ​

Next, create an external aspect log to provide extension logic for the properties and methods of Class ServiceTest

1. Cli command ​

bash
$ vona :create:bean aop log --module=demo-student

2. Menu command ​

TIP

Context Menu - [Module Path]: Vona Aspect/Aop

AOP Definition ​

typescript
import { BeanAopBase } from 'vona';
import { Aop } from 'vona-module-a-aspect';

@Aop({ match: 'demo-student.service.test' })
export class AopLog extends BeanAopBase {}
  • @Aop: This decorator is used to implement the external aspect
  • match: This option is used to associate the AopLog class with the ServiceTest class, which beanFullName is demo-student.service.test
nametypedescription
matchstring|regexp|(string|regexp)[]For which classes to enable

Aspect: Synchronous method ​

Outputs execution time for ServiceTest#actionSync

In the VSCode editor, enter the code snippet aopactionsync to automatically generate a code skeleton:

typescript
action: AopAction<ClassSome, 'action'> = (_args, next, _receiver) => {
  return next();
};

Adjust the code and add log logic.

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: Provides the actionSync method with the same name to implement custom logic

Aspect: Asynchronous method ​

Outputs execution time for ServiceTest#actionAsync

In the VSCode editor, enter the code snippet aopaction to automatically generate a code skeleton:

typescript
action: AopAction<ClassSome, 'action'> = async (_args, next, _receiver) => {
  return await next();
};

Adjust the code and add log logic.

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: Provides the actionAsync method with the same name to implement custom logic

Aspect: getter ​

Outputs execution time for ServiceTest#get name

In the VSCode editor, enter the code snippet aopgetter to automatically generate a code skeleton:

typescript
protected __get_xxx__: AopActionGetter<ClassSome, 'xxx'> = function (next, _receiver) {
  const value = next();
  return value;
};

Adjust the code and add log logic.

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__: Corresponding to the getter method get name of ServiceTest

Aspect: setter ​

Outputs execution time for ServiceTest#set name

In the VSCode editor, enter the code snippet aopsetter to automatically generate a code skeleton:

typescript
protected __set_xxx__: AopActionSetter<ClassSome, 'xxx'> = function (value, next, _receiver) {
  return next(value);
}

Adjust the code and add log logic.

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__: Corresponding to the setter method set name of ServiceTest

Aspect: __init__ ​

Outputs execution time for ServiceTest#__init__

In the VSCode editor, enter the code snippet aopinit to automatically generate a code skeleton:

typescript
protected __init__: AopActionInit<ClassSome> = (_args, next, _receiver) => {
  next();
};

Adjust the code and add log logic.

typescript
protected __init__: AopActionInit<ServiceTest> = (_args, next, _receiver) => {
  const timeBegin = Date.now();
  next();
  const timeEnd = Date.now();
  console.log('__init__: ', timeEnd - timeBegin);
};
  • __init__: Provides the __init__ method with the same name to implement custom logic

Aspect: __dispose__ ​

Outputs execution time for ServiceTest#__dispose__

In the VSCode editor, enter the code snippet aopdispose to automatically generate a code skeleton:

typescript
protected __dispose__: AopActionDispose<ClassSome> = async (_args, next, _receiver) => {
  await next();
};

Adjust the code and add log logic.

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__: Provides the __dispose__ method with the same name to implement custom logic

Aspect: __get__ ​

Extending magic method for ServiceTest

In the VSCode editor, enter the code snippet aopget to automatically generate a code skeleton:

typescript
protected __get__: AopActionGet<ClassSome> = (_prop, next, _receiver) => {
  const value = next();
  return value;
};

Adjust the code and add the custom field red

typescript
protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => {
  if (prop === 'red') return '#FF0000';
  const value = next();
  return value;
};
  • __get__: Conventional magic method name

Provide type definitions for colors using the interface type merging mechanism.

typescript
declare module 'vona-module-demo-student' {
  export interface ServiceTest {
    red: string;
  }
}

Aspect: __set__ ​

Extending magic method for ServiceTest

In the VSCode editor, enter the code snippet aopset to automatically generate a code skeleton:

typescript
protected __set__: AopActionSet<ClassSome> = (_prop, value, next, _receiver) => {
  return next(value);
};

Adjust the code to set the value for the custom field 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__: Conventional magic method name
  • If a value is set for prop, return true; otherwise, invoke next method

Then adjust the __get__ logic:

diff
protected __get__: AopActionGet<ServiceTest> = (prop, next, _receiver) => {
- if (prop === 'red') return '#FF0000';
+ if (prop === 'red') return this._colorRed;
  const value = next();
  return value;
}

Aspect: __method__ ​

Extending logic for any method of ServiceTest

In the VSCode editor, enter the code snippet aopmethod to automatically generate a code skeleton:

typescript
protected __method__: AopActionMethod<ClassSome> = (_method, _args, next, _receiver) => {
  return next();
};

Adjust the code and add log logic for actionSync and actionAsync

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__: Conventional magic method name
  • res?.then: Determine whether the return value is a Promise object and perform different processing to make it compatible with synchronous method and asynchronous method

AOP Order ​

For the same target Class, multiple AOPs can be associated. Therefore, VonaJS provides two parameters to control the execution order of AOPs.

1. dependencies ​

For example, has another AOP demo-student:log3, and we hope that the execution order is as follows: demo-student:log3 > Current

diff
@Aop({
  match: 'demo-student.service.test',
+ dependencies: 'demo-student:log3',
})
class AopLog {}

2. dependents ​

The order of dependents is just the opposite of dependencies. We hope that the execution order is as follows: Current > demo-student:log3

diff
@Aop({
  match: 'demo-student.service.test',
+ dependents: 'demo-student:log3',
})
class AopLog {}

AOP Enable/Disable ​

You can control enable/disable of AOPs.

1. Enable ​

src/backend/config/config/config.ts

diff
// onions
config.onions = {
  aop: {
    'demo-student:log': {
+     enable: false,
    },
  },
};

2. Meta ​

Allows AOPs to take effect in a specified operating environment.

NameTypeDescription
flavorstring|string[]See: Runtime Environments and Flavors
modestring|string[]See: Runtime Environments and Flavors
  • Example
diff
@Aop({
+ meta: {
+   flavor: 'normal',
+   mode: 'dev',
+ },
})
class AopLog {}

Inspect ​

You can directly inspect the currently effective AOP list in the target class action.

diff
class ServiceTest {
  protected async __dispose__() {
+   this.bean.onion.aop.inspect();
    this._name = '';
  }
  • this.bean.onion: Get the global Service instance onion
  • .aop: Get the Service instance related to AOP
  • .inspect: Output the currently effective AOP list

When the method is executed, the currently effective AOP list will be automatically output to the console, as shown below:

Released under the MIT License.