梦回苍石居  |
Cangshi Live

Annotate JS 757 Views

作者:
苍石
进度:
测试
Progress: 50%
项目开始时间:
2019-10-24
项目结束时间:
项目发表时间:
2020-06-23 11:40:12
最后编辑于:
3 年前
标签:
框架面向切面编程NODEJS注解JAVASCRIPT

查看演示

Annotate JS gitee.png github.png

Annotate JS 是一个基于 Javascript 中的注解提案 proposal-decorators 而实现的一套注解框架,我们可以通过这套框架实现类似 Java 中的依赖注入,以及面向切面编程等,适用于 Node 服务器与常规的 Javascript 开发。

[说明] proposal-decorators 提案经过了大幅修改,目前还没有定案,不知道语法会不会再变。本框架语法由于其特性以及需要 Babel 支持,所以采用的注解装饰器语法不是最新语法,需要配置 "legacy": true, 在以后该提案已经定案的情况下,会重现判断是否能够实现该框架需求从而决定是否对核心部分进行重构。
不过不用担心,由于 Babel 的存在,即使是新的语法已经推出,该框架也能正常使用。
最后附上该框架所使用的语法使用向导/原理:
· 装饰器 - ECMAScript 6入门 (国内)
· javascript-decorators


目录

快速开始

API文档

文档持续撰写中…


快速开始

引入

npm install @palerock/annotate-js

提前准备一个能使用 decorators 语法以及一些支持一些 javascript 的项目环境,如:

// 使用 babel 7 的 .babelrc 配置文件
{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ]
  ]
}

开始使用

使用 @Bean@Boot@Autowired 注解实现依赖注入

import {Bean, Boot, Autowired} from '@palerock/annotate-js'; 

/**
 * 声明一个组件名为 Demo
 */
@Bean
class Demo{
    sayHello(){
        console.log('hello annotate-js.');
    }
}

@Boot
class Init{

    @Autowired
    Demo; // 自动注入

    // 代码入口
    main(){
        this.Demo.sayHello(); // output `hello annotate-js.`
    }
}

使用 @Section 实现面向切面编程,将以上代码中的 Demo 类修改如下

import {Bean, Section} from '@palerock/annotate-js'; 

/**
 * 声明一个组件名为 Demo
 */
@Bean
class Demo{

    @Section(
       {
           before() {
               console.log('before say.');
           },
           after() {
               console.log('after say.');
           }
       }
    )
    sayHello(){
        console.log('hello annotate-js.');
    }
    // other code here...
}

运行最后输出 3 行文字:

before say.
hello annotate-js.
after say.

实现自定义注解

通过内置注解实现 @Annotate@DefaultParam 以及 @DynamicParam 生成简单注解:

import {
    Annotate,
    BeanDescribe,
    Boot,
    DefaultParam,
    DynamicParam,
    EnergyWire,
    SectionDescribe
} from "@palerock/annotate-js";


// 申明一个注解 ConfigurableBean 该注解继承了 BeanDescribe 也就是 @Bean
// 通过在定义成员变量改变其参数
@Annotate({extends: BeanDescribe})
class ConfigurableBean {

    // 将 args 设为默认参数,并且默认值为 [1,3,4]
    @DefaultParam
    args = [1, 3, 4];

    // 将 name 参数改为动态参数,所谓动态参数是通过一个方法,动态得到结果,这里是动态获取被装饰的 Class 的 name 并且将 Bean 参数的 name 值小写
    @DynamicParam
    name({classEntity}) {
        return classEntity.name.toLowerCase();
    }

}

@Annotate({extends: SectionDescribe})
class ParamsAppendMethodName {

    // 动态使被该注解装饰后的方法被调用时,将第一个参数加上含方法名的后缀
    @DynamicParam
    before({propertyEntity}) {
        return ({params}) => {
            params[0] = params[0] + ' - from method: ' + propertyEntity.name;
        }
    }
}

@ConfigurableBean // 测试默认值
class TestBean {
    constructor(...args) {
        this.args = args;
    }
}

@ConfigurableBean([3, 4, 5]) // 测试默认参数是否为 args
class TestBean1 {
    constructor(...args) {
        this.args = args;
    }

    @ParamsAppendMethodName // 测试方法参数是否添加了含方法名的后缀
    sayHello(word) {
        console.log('hello', word);
    }
}


@Boot
class Application {

    /**
     * 动态注入参数及方法
     */

    @EnergyWire('TestBean')
    args;

    @EnergyWire('TestBean1.args')
    args1;

    @EnergyWire('TestBean1')
    sayHello;

    main() {
        console.log(this.args);
        console.log(this.args1);
        this.sayHello('world');
    }
}

运行上述代码,输出为:

Decorator type [_AnonymousDescribe] works on the bean [testbean]
Decorator type [_AnonymousDescribe] works on the bean [testbean1]
Decorator type [BootDescribe] works on the bean [bootBean]
[ 1, 3, 4 ]
[ 3, 4, 5 ]
hello world - from method: sayHello

可以看到 beanName 全部小写了,并且默认参数和默认值都输出正常,最后添加含有方法名的后缀也正常运行。

通过以上生成注解的方法我们可以在已有注解的基础上改变其参数值生成新的注解,但是如果想要生成全新功能的注解需要参考以下方法:

使用 BeanDescribePropertyDescribe 通过 AnnotationGenerator 生成自定义注解:

  1. BeanDescribe 适用于声明在 Class 上,生成的注解默认和 @Bean 相同
  2. PropertyDescribe 适用于声明在 property 或者 method 上,默认没有任何功效
// annotation generator demo
import {BeanDescribe, PropertyDescribe, Boot, AnnotationGenerator} from '@palerock/annotate-js';

class MyBeanDescribe extends BeanDescribe {

    constructor() {
        super();
        Object.assign(this.params, {
            // 这里可以对注解的默认参数进行添加或修改
        });
    }

    onCreated() {
        super.onCreated();
        // 当该注解标记的 class 以及内部的 property 初始化完成后
    }

    /**
     * 注册 Proxy
     * @param proxy 用途如下所示
     */
    proxyRegister(proxy) {
        super.proxyRegister(proxy);
        // 这是使用 proxy 去注册时,是处于 proxy chain 的最后,如果前面的所有相关 proxy 都调用了 next 才能执行到这里
        proxy.register('get', ([...args], {next}) => {
            if (!Reflect.get(...args)) {
                return 'Hello Proxy';
            }
            next() // 必须调用 next 否则所有非空属性都会返回空
        })
    }


}

class MyPropertyDescribe extends PropertyDescribe {

    constructor() {
        super();
        Object.assign(this.params, {
            // 这里可以对注解的默认参数进行添加或修改
            tip: ''
        });
    }

    /**
     * 该方法对应每个被注解的 property
     * @param proxy 是一个注册器,实际用法参考如下代码
     * @param container{BasicBeanContainer} 是保存所有 bean 的容器,通过 bean name 可以获取到所有 bean
     */
    hookProperty({proxy, container}) {
        super.hookProperty({proxy, container});

        proxy.register('get', ([target, property, receiver], {next}) => {
            // 在这里劫持该 property 的 get 方法,用法于 Proxy 类似

            // 获取真实的属性值, 也就是你声明的值
            const realValue = Reflect.get(target, property, receiver);

            const params = this.params;

            if (typeof realValue === 'function') {
                // 如果真实属性是个函数, 则将该注解的参数注入到该函数中
                return function (args = {}) {
                    args.annotateParams = params;
                    return realValue.bind(this)(args);
                }
            }

            next(); // 如果不符合条件则交由下一个 register 的 get 处理
        });
    }

    /**
     * 该方法在属性所属的 class 完全生成后执行
     * 可以在这里执行一些需要在 class 生成后需要做的事
     * 或者获取 Bean 注解的一些属性
     * @param propertyEntity{PropertyEntity} 属性对象,见 /main/core/entities/PropertyEntity
     * @param classDecorator{BeanDescribe} 属性对应的 class 所被注解标记的注解描述
     */
    onClassBuilt(propertyEntity, classDecorator) {
        super.onClassBuilt(propertyEntity, classDecorator);
        // TO be override.
    }

}

const MyBean = AnnotationGenerator.generate(MyBeanDescribe);
const MyProperty = AnnotationGenerator.generate(MyPropertyDescribe);


@Boot
@MyBean
class BootEntry {

    finalProxyValue;

    @MyProperty({tip: 'Testing My Property'})
    test({annotateParams: {tip}}) {
        console.log(tip);
        return tip;
    }

    main() {
        // 入口
        // 注意这里没有传入 annotateParams 和 tip 参数
        const result = this.test(); // output: `Testing My Property`
        console.assert(result === 'Testing My Property');
        console.assert(this.finalProxyValue === 'Hello Proxy');
    }
}

项目目录扫描

环境要求:仅限 NodeJS 后台项目
在 NodeJS 环境中,我们可以使用 Scanner 对项目中的所有 JS 文件进行扫描,以实现加载多个 JS 文件中的多个组件
例:

import BasicScanner from "@palerock/annotate-js";

new BasicScanner().setContext(__dirname).scan(
    ['./examples/01-basic/*'] // 项目路径,* 是通配符
);

API文档

预置注解

@Bean

该注解用于生成组件

使用范围:仅 class

参数:

  • name
    • 类型:String
    • 默认值:class 名
    • 描述:该属性作为所装饰 class 的唯一标识
    • 注意事项:该属性对于整个项目是唯一的,不能有两个组件名是相同的
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • args
    • 类型:Array<Object>
    • 默认值:[]
    • 描述:在组件初始化时,该参数可以在生成组件时为构造函数赋予参数
    • 可选
  • isSectionSurround
    • 类型:Boolean
    • 默认值:true
    • 描述:该值为 true 时,组件可以自动识别 @Section 注解
    • 可选
  • containerType
    • 类型:Class
    • 默认值:BasicBeanContainer
    • 描述:该属性决定生成的组件所放入的容器,通过该容器可以获取该组件的实例
    • 可选

示例:

@Bean
class MyComponent {
  // 没有参数所有属性使用默认值, name = 'MyComponent'
}

@Bean('MyCmp')
class MyComponent1 {
  // 只有一个参数被设置,默认为 name 赋值, name = 'MyCmp'
}

@Bean({
    name:'CmpTest',
    args:[123]
})
class MyComponent2 {
  // 设置多个参数
  constructor(value) {
    console.log(value); // 123
  }
}

@Boot

该注解用于定义程序入口

功能及参数继承于 @Bean

默认参数:name

变动/额外参数:

  • name
    • 类型:String
    • 默认值:'bootBean'
    • 描述:同父级
    • 注意事项:在使用多个入口函数时,需要手动指定不同的 name
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • methodName
    • 类型:String
    • 默认值:'main'
    • 描述:指定该组件的入口方法名
    • 可选

@Property

该注解用于装饰成员属性,装饰该注解的成员属性可以被 @Bean 识别
若要自定义注解用于装饰成员属性,推荐使用该注解的 Describer
作用范围:ClassProperty

参数:

  • priority
    • 类型:Number
    • 默认值:0
    • 描述:在属性被多个该注解或者继承于该注解 Describer 所生成的注解装饰时,该参数可以指定注解被 @Bean 访问的优先级,值越大,优先级越高
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数

@Autowired

该注解继承于 @Property
该注解用于将指定 Bean 或其中的指定属性注入到该注解装饰的成员变量中
作用范围:仅 Property

默认参数:beanName

新增参数:

  • name
    • 类型:String
    • 默认值:被装饰变量的名字
    • 描述:在参数中 isMapProperty 不为 true 时,和 beanName 一致,若非,该参数作用和 propertyName 一致
    • 可选
  • beanName
    • 类型:String
    • 默认值:参数中 name 的值
    • 描述:被注入的 BeanName,指定该参数可以从所有已声明的 Bean 中获取与该属性匹配的组件
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • isMapProperty
    • 类型:Boolean
    • 默认值:false
    • 描述:是否注入指定 bean 中的某个成员属性,只有在参数 propertyNamename 不为空时生效
    • 可选
  • propertyName
    • 类型:String
    • 默认值:''
    • 描述:只有在参数中的 isMapPropertytrue 时生效,指定注入 bean 中的成员属性名
    • 可选

@EnergyWire

功能及参数继承于 @Autowired
值得注意的时,该注解专注于注入 bean 中的指定属性

默认参数:beanName

变动/额外参数:

  • name
    • 类型:String
    • 默认值:被装饰变量的名字
    • 描述:在参数中 isMapProperty 不为 true 时,和 beanName 一致,若非,该参数可以指定为链式字符串用于表示 beanName + propertyName
    • 注意事项: 链式表达式最多支持两层
    • 例如: 'MyBean.MyProperty'
    • 可选
  • beanName
    • 类型:String
    • 默认值:参数中 name 的值
    • 描述:被注入的 BeanName,指定该参数可以从所有已声明的 Bean 中获取与该属性匹配的组件
    • 新增:该属性支持链式表达,如 A.B 其中 A 表示 beanNameB 表示 propertyName
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • isMapProperty
    • 永远为 true 更改无效
    • 可选
  • propertyName
    • 类型:String
    • 默认值:被装饰的属性名
    • 描述:指定注入 bean 中的成员属性名
    • 可选

简单示例如下:

@Bean
class APIService {

    API_KEY = 'API Key';

    api01() {
        console.log('api 01 called', this.API_KEY);
    }

    api02() {
        console.log('api 02 called', this.API_KEY);
    }
}

@Boot
class TestBoot {

    @EnergyWire('APIService')
    api01;

    @EnergyWire({
        beanName: 'APIService',
        propertyName: 'api02'
    })
    api02;

    @EnergyWire('APIService.api02')
    api03;

    main() {
        this.api01(); // api 01 called API Key
        this.api02(); // api 02 called API Key
        this.api03(); // api 02 called API Key
    }
}

@Section

该注解继承于 @Property, 必须与 @Bean 一起使用
作用范围:classproperty,注意只对 Function 类型的 property 生效 当作用于 class 时,表示该 class 的所有 property 都作用于该注解

默认参数:priority 作用同父级

新增参数:

  • before
    • 类型:Function
    • 默认值:空方法
    • 描述:在被该注解装饰的方法被调用前时,调用该方法
    • 回调参数:(对象形式)
      • origin: 原方法 - Function
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Section 注解,外部的 Section 可以获取最近一次 Surround 的方法
      • 以下属性在 @Bean 属性 isSectionSurround == false 时无效
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象,
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值,
      • preventDefault: function 若调用该方法则阻止运行原方法,并且将 before 方法的返回值作为原方法返回值
    • 返回值: 无特殊用途
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • after
    • 类型:Function
    • 默认值:空方法
    • 描述:在被该注解装饰的方法被调用结束时,调用该方法
    • 回调参数:(对象形式)
      • origin: 原方法 - Function
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Section 注解,外部的 Section 可以获取最近一次 Surround 的方法
      • lastValue: 表示 lastOrigin 的返回值
      • 以下属性在 @Bean 属性 isSectionSurround == false 时无效
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象,
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值
    • 返回值: 代替源方法的返回值,如果外部还有 @Section 注解,则由 lastValue 的形式交由下一个 Section
    • 可选
  • onError
    • 类型:Function
    • 默认值:空方法
    • 描述:在调用该方法时出现错误时调用该方法(包括 before, after)
    • 回调参数:(对象形式)
      • error: 错误对象
      • resolve: 用于解决错误的方法,调用该方法表示不报错,参数表示正常的返回值 - Function - result
      • 以下属性在 @Bean 属性 isSectionSurround == false 时无效
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Surround 注解,外部的 Surround 可以获取最近一次 Surround 的方法
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值
      • preventDefault: function 若调用该方法则阻止运行原方法,并且将 before 方法的返回值作为原方法返回值
    • 返回值: 若 resolve 的参数为空,则使用该返回值
    • 可选
  • isAsync
    • 类型:Boolean
    • 默认值:'false''
    • 描述:该参数表示整个 @Section 是否异步允许(仅在 bean 属性 isSectionSurround == false 时生效),其它时候自动判断是否为异步
    • 注意:若由多个 @Section 注解装饰于同一个属性,其中任一一个注解被标示为 isAsync = true, 则所有的 @Section 注解都为异步运行
    • 可选

@Surround

作用和 @Section 类似,但该注解不依赖于注解 @Bean,在任何情况下都生效

作用范围:methodgetter/setter

注意:避免同时为同一 name 的 getter 方法和 setter 方法装饰效果相同的该注解,对于同一 name 的 getter/setter 注解会识别为同一属性。

错误示范:

class A {
  @Surround
  get name() {
      return this._name;
  }

  @Surround
  set name(value) {
      this._name = value;
  }
}

正确示范:

class A {
  get name() {
      return this._name;
  }

  @Surround
  set name(value) {
      this._name = value;
  }
}

class A {
  @Surround
  get name() {
    return this._name;
  }

  set name(value) {
      this._name = value;
  }
}

默认参数:before

参数:

  • before
    • 类型:Function
    • 默认值:空方法
    • 描述:在被该注解装饰的方法被调用前时,调用该方法
    • 回调参数:(对象形式)
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Surround 注解,外部的 Surround 可以获取最近一次 Surround 的方法
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值
      • preventDefault: function 若调用该方法则阻止运行原方法,并且将 before 方法的返回值作为原方法返回值
    • 返回值: 无特殊用途
    • 可选
    • 默认参数:在注解参数只有一个且不是对象的时候,标注为默认参数的注解属性会被赋值为该参数
  • after
    • 类型:Function
    • 默认值:空方法
    • 描述:在被该注解装饰的方法被调用结束时,调用该方法
    • 回调参数:(对象形式)
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Surround 注解,外部的 Surround 可以获取最近一次 Surround 的方法
      • lastValue: 表示 lastOrigin 的返回值
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象,
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值
    • 返回值: 代替源方法的返回值,如果外部还有 @Surround 注解,则由 lastValue 的形式交由下一个 Surround
    • 可选
  • onError
    • 类型:Function
    • 默认值:空方法
    • 描述:在调用该方法时出现错误时调用该方法(包括 before, after)
    • 回调参数:(对象形式)
      • params: 原方法调用的参数 - Object - {key:vlaue}
      • annotations: 该方法上的注解列表 - Array<Object>
      • propertyEntity: 属性实体,包含属性名等信息
      • lastOrigin: 若同时有多个 @Surround 注解,外部的 Surround 可以获取最近一次 Surround 的方法
      • isGetter: boolean 当前方法是否为 Getter,
      • isSetter: boolean 当前方法是否为 Setter,
      • annotate: Object 当前注解对象
      • trans: Object 表示当前事物的缓存变量,更改该变量可以让之后执行的方法 afteronError 获取改变后的值
      • error: 错误对象
      • resolve: 用于解决错误的方法,调用该方法表示不报错,参数表示正常的返回值 - Function - result
    • 返回值: 若 resolve 的参数为空,则使用该返回值
    • 可选
  • isAsync 已移除

@Annotate

该注解用于生成新的注解,需要作用与 class,新注解的名字即 class 名,使用方法如下:

// 将注解装饰在需要生成的 class 上
@Annotate
class MyAnnotate {

    /**
     * 参数区域
     */

    a = 10;

    b = 100;
}

// 使用生成好的注解
@MyAnnotate({a: 50})
class TestClass {}

通过以上代码我们定义了注解 @Annotate 并且将其装饰在 TestClass,并赋值参数 a50

注意:通过该注解生成的注解目前无法被继承

作用范围:class

默认参数:无

注解参数:

  • extends
    • 表示生成的注解继承于哪个注解,可以是 *Describe 类型,也可以是某个注解本身
    • 默认:BasicAnnotationDescribe
    • 可选

@DefaultParam

必须与 @Annotate 配合使用,只能作用于成员变量,若被该注解装饰表示生成的注解的默认参数即为该注解装饰的变量,例:

@Annotate
class MyAnnotate {

    /**
     * 参数区域
     */

    a = 10;

    @DefaultParam
    b = 100;
}

// 默认参数 b 值被设置为 50
@MyAnnotate(50)
class TestClass {}

注意:若同一注解多个参数装饰该注解,只有第一个生效

@DynamicParam

必须与 @Annotate 配合使用,只能作用于类型为 function 的成员变量,若被该注解装饰表示该参数是一个动态方法,所谓动态方法即表示该方法的返回值才是该参数最终的值,该方法接受以下参数(对象形式):

  • classEntity
    • 表示将被该注解装饰的 class 实体对象
    • 只在生成注解作用于 class 时生效
  • propertyEntity
    • 表示将被该注解装饰的成员变量实体对象
    • 只在生成注解作用于成员变量时生效 例:
@Annotate
class MyAnnotate {

    /**
     * 参数区域
     */

    a = 10;

    @DynamicParam
    b({classEntity}){
        return classEntity.name;
    }
}

// 参数 b 的值将会被设置为 'TestClass'
@MyAnnotate
class TestClass {}

文档持续撰写中…

留言 ( 0 )

Person name
未登录用户可以发表匿名评论