基于原生JS实现的Bean容器和AOP编程
Bean是什么
我们知道Bean是Spring最基础的核心构件,大多数逻辑代码都通过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.atom、ctx.bean.user、ctx.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/ctx |
| bean | bean组件 |
| global | 是否是全局组件 |
使用Bean
1. beanFullName
每一个注册的Bean组件都被分配了全称,具体规则如下
| 注册名称 | 场景 | 所属模块 | global | beanFullName |
|---|---|---|---|---|
| test.app | test | test-party | false | test-party.test.app |
| testctx | test-party | true | testctx |
全局Bean(
global:true): 当一个Bean组件可以作为一个核心的基础组件的时候,可以设置为全局Bean,方便其他模块的调用,比如:atom、user、role、flow、flowTask,等等
本地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.locale |
| subdomain | 可选 | 默认等于ctx.subdomain |
| beanModule | 必需 | 本地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
相关链接
基于原生JS实现的Bean容器和AOP编程的更多相关文章
- 用jQuery基于原生js封装的轮播
我发现轮播在很多网站里面都用到过,一个绚丽的轮播可以为网页增色不少,最近闲来无事,也用原生js封装了一个轮播,可能不像网上的插件那么炫,但是也有用心去做.主要用了闭包的思想.需要传递的参数有:图片地址 ...
- 基于原生js的图片延迟加载
当页面图片比较多的时候,我们通常会做一个延迟加载,避免页面打开时一下子的请求数太多,加载过慢影响用户体验. 如果项目用了jquery框架,则可以直接用 jquery.lazyload.可在jquery ...
- 基于原生js的返回顶部组件,兼容主流浏览器
基于原生js的返回顶部插件,兼容IE8及以上.FF.chrome等主流浏览器. js文件中封装了getScrollTop()和changeScrollTop()函数分别用于获取滚动条滚动的高度和修改滚 ...
- 基于原生JS的jsonp方法的实现
基于原生JS的jsonp方法的实现 jsonp,相信大家并不陌生,是在js异步请求中解决跨域的方法之一,原理很简单,有不清楚的同学可以google下,这里就补详细解释了.在Jquery库中,jQuer ...
- 基于原生JS封装数组原型上的sort方法
基于原生JS封装数组原型上的sort方法 最近学习了数组的原型上内置方法的封装,加强了用原生JS封装方法的能力,也进一步理解数组方法封装的过程,实现的功能.虽然没有深入底层,了解源码.以下解法都是基于 ...
- 基于three.js实现特定Div容器的粒子特效封装
本文基于three.js实现特定容器的粒子特效效果,支持用户传入特定的dom对象以及粒子颜色. 效果图 移入库 <script src="jquery-1.11.3.min.js&qu ...
- 前后端数据交互处理基于原生JS模板引擎开发
json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那 ...
- 转载 -- 基于原生JS与OC方法互相调用并传值(附HTML代码)
最近项目里面有有个商品活动界面,要与web端传值,将用户在网页点击的商品id 传给客户端,也就是js交互,其实再说明白一点就是方法的互相调用而已. 本文叙述下如何进行原生的JavaScript交互 本 ...
- js:基于原生js的上啦下啦刷新功能
链接:https://www.jianshu.com/p/a8392115e6f0演示地址:http://wonghan.cn/iscroll-demo/html:<body> <d ...
随机推荐
- spring总览
Spring 概述 1. 什么是spring? Spring 是个java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring ...
- MVVM模式-数据的双向绑定
- 【UWP】实现一个波浪进度条
好久没写 blog 了,一个是忙,另外一个是觉得没啥好写.废话不多说,直接上效果图: 可能看到这波浪线你觉得会很难,但看完这篇 blog 后应该你也会像我一样恍然大悟.图上的图形,我们可以考虑是由 3 ...
- PyQt5 基础知识(六):展示控件
目录 3. 展示控件 3.1 QLabel 3.1.1 描述 3.1.2 功能作用 3.1.2.1 基本功能 3.1.2.2 文本交互 3.1.2.3 内容操作 3.1.2.3.1 文本字符串 3.1 ...
- c++对c的拓展_编译检测的增强
一:全局变量检测增强:c++编译对全局变量的声明定义有严格区别 //c中全局变量的声明及定义 //c++中全局变量的声明及定义 //c++全局变量写成下列形式则报错 in ...
- 厉害!我带的实习生仅用四步就整合好SpringSecurity+JWT实现登录认证!
小二是新来的实习生,作为技术 leader,我还是很负责任的,有什么锅都想甩给他,啊,不,一不小心怎么把心里话全说出来了呢?重来! 小二是新来的实习生,作为技术 leader,我还是很负责任的,有什么 ...
- 为什么说TCP协议是可靠的?
TCP 实现可靠通信的两种方式 我们都知道 IP 协议是"不太靠谱".因为 IP 协议是不可靠的,所以 IP 数据包可能在传输过程中发生错误或者丢失.这就意味着,TCP 协议不得不 ...
- 数据库基础知识详解三:MVCC、范式以及表连接方式
写在文章前:本系列文章用于博主自己归纳复习一些基础知识,同时也分享给可能需要的人,因为水平有限,肯定存在诸多不足以及技术性错误,请大佬们及时指正. 8.MVCC 多版本并发控制(Multi-Versi ...
- 基于HiKariCP组件,分析连接池原理
HiKariCP作为SpringBoot2框架的默认连接池,号称是跑的最快的连接池,数据库连接池与之前两篇提到的线程池和对象池,从设计的原理上都是基于池化思想,只是在实现方式上有各自的特点:
- 11.Flink实时项目之支付宽表
支付宽表 支付宽表的目的,最主要的原因是支付表没有到订单明细,支付金额没有细分到商品上, 没有办法统计商品级的支付状况. 所以本次宽表的核心就是要把支付表的信息与订单明细关联上. 解决方案有两个 一个 ...