Rockerjs Core

基于 TypeScript 和注解的轻量级IoC容器,提供了依赖注入、面向切面编程及异常处理等功能。Rockerjs Core可在任意工程中引入,是一个框架无关的IoC容器。

@rockerjs/core模块不依赖于任何框架,并与现有框架、库、类等保持兼容。通过DI(Dependency Injection)实现代码解耦和依赖解耦,在构建复杂应用时保证可扩展性与灵活性;同时提供二维编程的能力,基于注解可在各个连接点(Advice)进行非核心业务的操作,减少代码冗余;最后,它提供一种基于注解配置的简易异常处理机制 -- Clamp机制,通过特定规则匹配异常处理程序实现处理。

一、快速上手

安装

npm install @rockerjs/core

@rockerjs/core最佳实践需要结合TypeScript的装饰器一起使用(也可使用接口),因此需要在项目根目录添加 tsconfig.json 文件,并配置编译选项 “experimentalDecorators”和“emitDecoratorMetadata”为 true

示例 1

import { Container, Inject } from '@rockerjs/core';

class User {
id: string = "testId";
name: string = "testName";
} class UserService {
getUser(_id: string): User {
return new User();
}
} @Inject
class ControlDefault {
@Inject
userService: UserService; test() {
let user: User = this.userService.getUser("test");
console.log(user);
}
} @Inject('controllor-with-args', new Date())
class ControlDefaultWithArgs {
name: string;
time: Date; constructor(name: string, time: Date) {
this.name = name;
this.time = time;
} @Inject
userService: UserService; test() {
let user: User = this.userService.getUser("test");
console.log(user, this.name, this.time);
}
} @Inject('controllor1', 'util', new Date())
class Control {
name: string;
time: Date; constructor(name: string, time: Date) {
this.name = name;
this.time = time;
} @Inject
userService: UserService; test() {
let user: User = this.userService.getUser("test");
console.log(user, this.name, this.time);
}
} // 通过getObject接口从容器中获取实例,参数为“单例的名称”(默认名称为类名首字母小写)
Container.getObject<ControlDefault>('controlDefault').test();
// 通过getObject接口从容器中获取实例,此例中并未提供实例名
Container.getObject<ControlDefaultWithArgs>('controlDefaultWithArgs').test();
// 通过getObject接口从容器中获取实例,此例中提供了3个参数,@rockerjs/core 认为第一个参数为实例名,剩下的参数则用于实例化
Container.getObject<Control>('controllor1').test();

示例 2 : RPC

import {Container, Inject} from '@rockerjs/core';

//PRC Demo实现
let RPC = {
config: function (cfg: { serviceUrl: string, interfaces: Function[] }) {
if (cfg.interfaces) {
cfg.interfaces.forEach((type: FunctionConstructor) => {
if (type.prototype) {
let newObj = {}, proto = type.prototype;
let nms = Object.getOwnPropertyNames(proto);
if (nms) {
nms.forEach((nm) => {
if (nm != 'constructor' && typeof(proto[nm]) === 'function') {
newObj[nm] = function () {
//{nm:方法名,arguments:参数表},改为调用远程请求过程
return arguments[0];//test return
}
}
})
}
Container.provides([type, () => {
return newObj;
}])
}
})
}
}
} //--DEMO-------------------------------------------------------- //1. 接口声明(注意,此处只能使用Concrete class)
class Product {
getById(id: string): string {
return null;
}
} //2. 应用RPC Framework
RPC.config({
serviceUrl: null,
interfaces: [Product]//提供接口描述,在RPC中构建factory
}) //3. Service class
@Inject
class Service {
@Inject
product: Product; test() {
let id: string = 'tid';
let rst = this.product.getById(id);
console.log(rst);
}
} //4.测试
Container.getObject<Service>('service').test();

二、依赖注入与容器

依赖注入 @Inject

提供了注解 @Inject 来实现依赖的注入,当我们有如下 GetDubboData 类时

class GetDubboData {
p0: number;
constructor(p0: number, p1: string) {
this.p0 = p0;
}
}

我们可以通过以下方式实例化这个类,同时传入指定的参数

  1. 直接传递构造函数的参数

    class SomeControl {
    @Inject(1, 'aaa')
    private dubbo: GetDubboData
    }
  2. 给出构造函数的工厂函数

    class SomeControl {
    @Inject(function () {
    return [1, 'aaa']
    })
    private dubbo: GetDubboData
    }
  3. 无构造函数或参数为空

    class SomeControl {
    @Inject
    private dubbo: GetDubboData
    }

操作类实例化容器

默认的实例化方法可以满足开发者的大部分需求,Rockerjs Core 提供了 provides 方法自定义实例化工厂,同时提供了获取类和类实例化函数映射表的方法。

注册、修改类的实例化方法

  • 直接传入类或工厂函数

    // 形式一:形如 Container.provides(class extends UserService{})
    Container.provides(
    class extends UserService {
    getUser(id: string): User {
    console.log(1);
    return new User();
    }
    }
    );
  • 传入类及类的工厂函数

    // 形式二:形如 Container.provides([UserService,FactoryFunction])
    Container.provides([
    UserService,
    () => {
    return new class extends UserService {
    getUser(id: string): User {
    console.log(2);
    return new User();
    }
    }();
    }
    ]);

获取实例化方法注册表

getGeneralHashmap()

返回一个构造函数-工厂方法映射表, 结构如下

const globalGeneralProviders: Map<FunctionConstructor, Function> = new Map<
FunctionConstructor,
Function
>();

手动实例化方法

Container.injectClazzManually 方法提供了直接实例化注册表中的类的功能,参数为构造函数以及想要传入的参数

class SomeControl {
transGet: GetTransData = Container.injectClazzManually(GetTransData, 1, 2); async getProduct(_productId?: number) {
let json: any = await this.transGet.getDetail(_productId);
console.log(json);
}
}

完整例子

假设我们有一个获取异步数据的抽象类

abstract class GetTransData {
p0: number
constructor(p0: number, p1: string) {
console.log(p0 + p1)
this.p0 = p0
} abstract async getDetail(_proId: number): Promise<string>;
}

可以通过 Container 的 provides API 来指定对应类型的工厂函数

Container.provides([GetTransData, (_p0, _p1) => {
return new class extends GetTransData {
constructor(p0: number, p1: string) {
super(p0, p1);
} async getDetail(_id: number): Promise<string> {
await ((ms) => new Promise(res => setTimeout(res, ms)))(100)
return `Hello ${this.p0}`
}
}(_p0, _p1);
}]);

最终通过 @Inject 方法注入在测试类里面实例化这个对象

@Inject
class SomeControl {
@Inject(666, 2)
transGet: GetTransData; async getProduct(_productId?: number) {
let json: any = await this.transGet.getDetail(_productId);
console.log(json);
}
} Container.getObject<SomeControl>('someControl').getProduct();

得到输出结果

668
Hello 666

三、面向切面编程 AOP

面向切面编程(AOP是Aspect Oriented Program的首字母缩写)是指在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。Rockerjs Core 提供了 AOP 编程能力

简单的例子

假如我们想在下面的 foo 方法执行前后打点

class Test {
foo() {
console.log('foo')
}
} new Test().foo()

我们可以声明一个日志类,通过@Aspect注解声明其为一个切面类,通过@Pointcut注解配置想要匹配的类、方法以及需要执行的钩子, 最后通过 @Before@After等注解标识被修饰方法在处于对应生命周期时需要执行的方法

import { Aspect, Pointcut, Before, After } from "@rockerjs/core";

@Aspect
class Logger {
// 必须在静态方法上注册切点
@Pointcut({
clazz: Test, // 定位被修饰的类
rules: ".*foo.*", // 通过正则匹配到对应的方法,不填则匹配所有函数
advices: ["before:printStart", "after"] // 过滤将要执行的钩子 (可细致到函数名)
})
static main() {} // 在执行被打点方法前执行的方法
@Before
printStart() {
console.log("log:start:", new Date());
} // 可以指定多个方法
@Before
printStart2() {
console.log("log:start:", new Date());
} // 在执行被打点方法后执行的方法
@After
printEnd() {
console.log("log:end:", new Date());
}
}

必须在切面类的静态方法上注册切点

Advices(可理解为生命周期,下文用生命周期代指advice)列表

Rockerjs Core 提供了Before, After,After_Throwing, After_Returning,Around等生命周期

  • Before:在被打点函数执行前执行
  • After:在被打点函数执行后执行
  • After_Throwing:在被打点函数抛出异常时执行
  • After_Returning:在被打点函数返回结果后执行
  • Around:在被打点函数执行前后执行,类似于 koa 中间件

@After_Returning

  1. 在 after 后执行
  2. 如果原生函数没有 return 任何东西则不执行
  3. 可以修改返回结果
@After_Returning
printReturn(ctx, result) {
// ctx 为函数执行上下文
// result 为函数执行的结果
result += 666
return result
}

@After_Throwing

@After_Throwing
printthrow(ctx, ex) {
// ctx 为函数执行上下文
// ex 为错误信息
console.log('Loggy catch: '+ ex.message);
console.log(ctx)
}

@Around

@Around
currentTime2(ctx, next) {
// ctx 为函数执行上下文
// next 为匹配到的函数
console.log('before',Date.now());
let ret = next();
console.log('after',Date.now(),ret);
return ret;
}

切面组合

我们可以为某个类同时注册多个切面类,再通过 composeAspects 方法将它们组合起来,默认按照声明的顺序来包裹被打点的函数,最后声明的类会包裹在最外面一层

@Aspect()
class Logger {
// ...
} @Aspect()
class Logger2 {
@Pointcut({
clazz: Test,
advices: ["before", "after", "around", 'after_returning']
})
static main() {} @Before
printStart() {
console.log("2:start");
} @After
printafter() {
console.log("2:after");
} @After_Returning
printReturn(ctx, result) {
console.log('2:after_returning', result)
return result + 2
} @Around
printAround2(ctx, next) {
console.log("2:around:before");
let ret = next();
console.log("2:around:after", ret);
return ret;
}
} @Aspect()
class Logger3 {
// ...
} composeAspects({
clazz: Test,
// rules: '.*foo.*',
aspects: [Logger, Logger2, Logger3]
});

执行结果如下:

3:start
2:start
1:start
3:around:before
2:around:before
1:around:before
foo
1:around:after bar
2:around:after bar
3:around:after bar
1:after
2:after
3:after
1:after_returning bar
2:after_returning bar
3:after_returning bar

如果想自定义切面之间执行的顺序,可以在切面注解上传入切面的次序(数值小的在洋葱模型的外层):

@Aspect({
order: 2
})
class Logger { } @Aspect({
order: 1
})
class Logger2 { } @Aspect({
order: 3
})
class Logger3 { } composeAspects({
clazz: Test,
aspects: [Logger, Logger2, Logger3]
});

执行顺序如下:

2:start
1:start
3:start
2:around:before
1:around:before
3:around:before
foo
3:around:after bar
1:around:after bar
2:around:after bar
3:end
1:end
2:end

四、异常处理 Exception

除了通过 Rockerjs Core AOP 中的 @After_Throwing 注解来实现错误捕获,我们还提供了更简便的实现错误捕获的方法,如下例,我们先声明一个错误捕获夹,然后在被包裹的函数上使用这个错误捕获夹,当函数执行过程中有异常发生时,我们能在捕获夹的 catch 方法中拿到错误信息以及函数执行的上下文。

import { Container, Inject, Catch, Clamp, ExceptionClamp } from "@rockerjs/core";

// 1. 声明一个捕获器,实现 catch 方法
@Clamp
class Clamper extends ExceptionClamp {
catch(ex, ctx) {
console.log("hahaha: ****", ex, ctx);
}
} @Inject
class Test {
// 2. 使用捕获器
@Catch("Clamper")
test() {
throw new Error("12322");
}
} Container.getObject<Test>('test').test();

@After_Throwing 同时使用时,@Catch 会先捕获到错误,再次将错误抛出, @After_Throwing 才捕获到错误

@Clamp
class Clamper extends ExceptionClamp {
catch(ex, ctx) {
console.log("hahaha: ****", ex, ctx);
throw ex // 将错误二次抛出
}
} @Inject
class Test {
@Catch("Clamper")
test() {
throw new Error("12322");
}
} @Aspect
class ExceptionClamp2 {
@Pointcut({
clazz: Test,
advices: ['after_throwing']
})
static main() {} @After_Throwing
printThrow(ctx, ex) {
console.log('Loggy catch: '+ ex.message);
console.log(ctx)
}
} Container.getObject<Test>('test').test();

Contribute

请参考 Contribute Guide 后提交 Pull Request。

面向复杂应用,Node.js中的IoC容器 -- Rockerjs/core的更多相关文章

  1. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  2. 如何在Node.js中合并两个复杂对象

    通常情况下,在Node.js中我们可以通过underscore的extend或者lodash的merge来合并两个对象,但是对于像下面这种复杂的对象,要如何来应对呢? 例如我有以下两个object: ...

  3. Node.js中的Session,不要觉得简单哦。

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,博客地址为http://www.cnblogs.com/jasonnode/ .学习网站上有对应 ...

  4. Node.js 中MongoDB的基本接口操作

    Node.js 中MongoDB的基本接口操作 连接数据库 安装mongodb模块 导入mongodb模块 调用connect方法 文档的增删改查操作 插入文档 方法: db.collection(& ...

  5. 在node.js中使用COOKIE

    node.js中如何向客户端发送COOKIE呢?有如下两个方案: 一.使用response.writeHead,代码示例: //设置过期时间为一分钟 var today = new Date(); v ...

  6. 初步揭秘node.js中的事件

    当你学习node.js的时候,Events是一个非常重要的需要理解的事情.非常多的Node对象触发事件,你能在文档API中找到很多例子.但是关于如何写自己的事件和监听,你可能还不太清楚.如果你不了解, ...

  7. Node.js权威指南 (10) - Node.js中的错误处理与断言处理

    10.1 使用domain模块处理错误 / 272 10.1.1 domain模块概述 / 272 10.1.2 创建并使用Domain对象 / 274 10.1.3 隐式绑定与显式绑定 / 276 ...

  8. Node.js中的URL

    Node.js中的URL 什么是URL URL是Uniform Location Resource的缩写,翻译为"统一资源定位符",也就是描述资源位置的固定表示方法.被URL描述的 ...

  9. mysql语句在node.js中的写法

    总结一下mysql语句在node.js中的各种写法,参考了npm网站mysql模块给的实例. 查询 select //1 db.query('select * from tuanshang_users ...

随机推荐

  1. Spring Session event事件分析

    1. org.apache.catalina.session.StandardSession 这是servlet-api jar包中的一个类.是session接口的标准实现.当session创建的时候 ...

  2. WCF部署失败

    HTTP 错误 404.3 - Not Found 由于扩展配置问题而无法提供您请求的页面.如果该页面是脚本,请添加处理程序.如果应下载文件,请添加 MIME 映射. 最可能的原因:•可能是缺少处理程 ...

  3. python numpy库的基本内容

    import numpy as np np.getfromtxt("路径",delimiter = "," ,dtype = str)  #读取txt文件数据 ...

  4. linux touch命令 创建文件

    touch 创建文件,用法,touch test.txt,如果文件存在,则表示修改当前文件时间 [root@MongoDB ~]# touch /data/text.txt [root@MongoDB ...

  5. 20190423 PowerDesigner 数据库模型快速建立

    后面我在做一个视频的讲解记录吧! 那种讲解记录,只是为了演示按钮功能在什么地方,这个功能的作用是什么 这个软件相对比较简单的使用步骤,主要有三步 第一. 选择好你针对的数据库版本和类型创建数据库名称基 ...

  6. ADB——查看手机设备信息

    查看设备信息 查看手机型号 adb shell getprop ro.product.model 查看电池状况 adb shell dumpsys battery ''' Current Batter ...

  7. zabbix实现自定义监控

    实现自定义监控项实例 .创建主机组 .创建主机 .创建监控项 .到需要监控的主机的agent中添加自定义的监控项目 cd /etc/zabbix/zabbix_agentd.d vi userpara ...

  8. Spark SQL历险记

    现在的spark sql编程通常使用scala api 以及 java api的方式,相比于直接使用 spark sql语句,spark api灵活很多,毕竟可以基于dataset以及rdd两种方式进 ...

  9. bootstrap4 Reboot details summary 美化(点选禁止选中文本,单行隐藏角标,多行后移)

    bootstrap4 Reboot details summary 优化这块,主要是为了去掉details summary的角标~ 所以优化写了一下. 原始HTML <details> & ...

  10. 我的第三篇博客(激动激动真激动!!!)A-B Problem

    #210. 差(A-B problem) 题目描述 楠楠在网上刷题,感觉第一题:求两数的和(A+B Problem)太无聊了,于是增加了一题:A-B Problem,难倒了一群小朋友,哈哈. 题目是这 ...