基于原生JS实现的Bean容器和AOP编程

码农天地 -
基于原生JS实现的Bean容器和AOP编程
Bean是什么

我们知道BeanSpring最基础的核心构件,大多数逻辑代码都通过Bean进行管理。NestJS基于TypeScript依赖注入也实现了类似于Spring Bean的机制:服务提供者(Provider)

CabloyJS则是在原生JS(Vanilla JS)上实现了更轻量、更灵活的Bean容器

理念

CabloyJS在设计Bean容器机制时,遵循了以下3个理念:

1. 几乎所有事物都是Bean

我们绝大多数逻辑代码都通过Bean组件进行管理,比如:Controller、Service、Model、Middleware、Event、Queue、Broadcast、Schedule、Startup、Flow、Flow Task,等等

CabloyJS 4.0在实现了Bean容器之后,基本上所有核心组件都以Bean为基础进行了重构。比如基于EggJS的Controller、Service、Middleware,也实现了Bean组件化2. Bean支持AOP

所有Bean组件都可以通过AOP组件进行逻辑扩展

3. AOP也是一种Bean

AOP组件既然也是Bean,那么也可以通过其他AOP组件进行逻辑扩展

这种递归设计,为系统的可定制性和延展性,提供了强大的想象空间定义Bean

CabloyJS约定了两种定义Bean的模式:app和ctx。由于Bean被容器托管,可以很方便的跨模块调用。因此,为了清晰的辨识Bean被应用的场景,一般约定:如果Bean只被本模块内部调用,那么就使用app模式;如果大概率会被其他模块调用,那么就使用ctx模式

1. app模式

比如:Controller、Service都采用app模式

src/module/test-party/backend/src/bean/test.app.js

module.exports = app => {

  class appBean extends app.meta.BeanBase {

    actionSync({ a, b }) {
      return a + b;
    }

    async actionAsync({ a, b }) {
      return Promise.resolve(a + b);
    }

  }

  return appBean;
};
2. ctx模式

比如:ctx.bean.atomctx.bean.userctx.bean.role都采用ctx模式

src/module/test-party/backend/src/bean/test.ctx.js

module.exports = ctx => {
  class ctxBean {

    constructor(moduleName) {
      this._name = moduleName || ctx.module.info.relativeName;
    }

    get name() {
      return this._name;
    }

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

    actionSync({ a, b }) {
      return a + b;
    }

    async actionAsync({ a, b }) {
      return Promise.resolve(a + b);
    }

  }

  return ctxBean;
};
ctx.module.info.relativeName: 由于ctx模式的Bean经常被其他模块调用,那么可以通过此属性取得调用方模块的名称注册Bean

对于大多数组件,EggJS采用约定优先的策略,会在指定的位置查找资源,并自动加载。而CabloyJS采用显式注册,从而Webpack可以收集所有后端源码,实现模块编译的特性

src/module/test-party/backend/src/beans.js

const testApp = require('./bean/test.app.js');
const testCtx = require('./bean/test.ctx.js');

module.exports = app => {
  const beans = {
    // test
    'test.app': {
      mode: 'app',
      bean: testApp,
    },
    testctx: {
      mode: 'ctx',
      bean: testCtx,
      global: true,
    },
  };
  return beans;
};
名称说明mode模式:app/ctxbeanbean组件global是否是全局组件使用Bean1. beanFullName

每一个注册的Bean组件都被分配了全称,具体规则如下

注册名称场景所属模块globalbeanFullNametest.apptesttest-partyfalsetest-party.test.apptestctx test-partytruetestctx全局Bean(global:true): 当一个Bean组件可以作为一个核心的基础组件的时候,可以设置为全局Bean,方便其他模块的调用,比如: atomuserroleflowflowTask,等等

本地Bean(global:false): 当一个Bean组件一般只用于本模块时,可以设置为本地Bean,从而避免命名冲突

场景:对于本地Bean,我们一般为其分配一个场景名称作为前缀,一方面便于Bean的分类管理,另一方面也便于辨识Bean的用途

2. 基本调用

可以直接通过this.ctx.bean取得Bean容器,然后通过beanFullName获取Bean实例

src/module/test-party/backend/src/controller/test/feat/bean.js


  // global: false
  this.ctx.bean['test-party.test.app'].actionSync({ a, b }); 
  await this.ctx.bean['test-party.test.app'].actionAsync({ a, b });

  // global: true
  this.ctx.bean.testctx.actionSync({ a, b });
  await this.ctx.bean.testctx.actionAsync({ a, b });
3. 新建Bean实例

通过this.ctx.bean获取Bean实例,那么这个实例对当前ctx而言是单例的。如果需要新建Bean实例,可以按如下方式进行:

ctx.bean._newBean(beanFullName, ...args)

比如我们要新建一个Flow实例:

src/module-system/a-flow/backend/src/bean/bean.flow.js

    _createFlowInstance({ flowDef }) {
      const flowInstance = ctx.bean._newBean(`${moduleInfo.relativeName}.local.flow.flow`, {
        flowDef,
      });
      return flowInstance;
    }
4. 跨模块调用本地Bean

本地Bean也可以被跨模块调用

跨模块调用的本质:新建一个ctx上下文环境,该ctx的module信息与本地Bean一致,然后通过新容器ctx.bean来调用本地Bean
await ctx.executeBean({ locale, subdomain, beanModule, beanFullName, context, fn, transaction })
名称可选说明locale可选默认等于ctx.localesubdomain可选默认等于ctx.subdomainbeanModule必需本地Bean所属模块名称beanFullName必需本地Bean的全称context可选调用本地Bean时传入的参数fn必需调用本地Bean的方法名transaction可选是否要启用数据库事务

比如我们要调用模块a-file的本地Bean: service.file,直接上传用户的avatar,并返回downloadUrl

src/module-system/a-base-sync/backend/src/bean/bean.user.js

      // upload
      const res2 = await ctx.executeBean({
        beanModule: 'a-file',
        beanFullName: 'a-file.service.file',
        context: { fileContent: res.data, meta, user: null },
        fn: '_upload',
      });
      // hold
      profile._avatar = res2.downloadUrl;
5. app.bean

ctx.bean是每个请求初始化一个容器,而app.bean则可以实现整个应用使用一个容器,从而实现Bean组件的应用级别的单例模式

src/module/test-party/backend/src/controller/test/feat/bean.js

  app.bean['test-party.test.app'].actionSync({ a, b }); 
  await app.bean['test-party.test.app'].actionAsync({ a, b });
AOP编程

限于篇幅,关于AOP编程请参见:cabloy-aop

相关链接官网: https://cabloy.com/GitHub: https://github.com/zhennann/cabloy
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。
下一篇: 百度疫情tab

Tags 标签

加个好友,技术交流

1628738909466805.jpg