基于 TypeScript 和注解的轻量级IoC容器,提供了依赖注入、面向切面编程及异常处理等功能。Rockerjs Core可在任意工程中引入,是一个框架无关的IoC容器。html
@rockerjs/core模块不依赖于任何框架,并与现有框架、库、类等保持兼容。经过DI(Dependency Injection)实现代码解耦和依赖解耦,在构建复杂应用时保证可扩展性与灵活性;同时提供二维编程的能力,基于注解可在各个链接点(Advice)进行非核心业务的操做,减小代码冗余;最后,它提供一种基于注解配置的简易异常处理机制 -- Clamp机制,经过特定规则匹配异常处理程序实现处理。git
安装github
npm install @rockerjs/core
@rockerjs/core最佳实践须要结合TypeScript的装饰器一块儿使用(也可以使用接口),所以须要在项目根目录添加 tsconfig.json 文件,并配置编译选项 “experimentalDecorators”和“emitDecoratorMetadata”为 truetypescript
示例 1npm
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
来实现依赖的注入,当咱们有以下 GetDubboData
类时json
class GetDubboData { p0: number; constructor(p0: number, p1: string) { this.p0 = p0; } }
咱们能够经过如下方式实例化这个类,同时传入指定的参数框架
直接传递构造函数的参数koa
class SomeControl { @Inject(1, 'aaa') private dubbo: GetDubboData }
给出构造函数的工厂函数异步
class SomeControl { @Inject(function () { return [1, 'aaa'] }) private dubbo: GetDubboData }
无构造函数或参数为空
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(); } }(); } ]);
返回一个构造函数-工厂方法映射表, 结构以下
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是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()); } }
必须在切面类的静态方法上注册切点
Rockerjs Core 提供了Before
, After
,After_Throwing
, After_Returning
,Around
等生命周期
@After_Returning printReturn(ctx, result) { // ctx 为函数执行上下文 // result 为函数执行的结果 result += 666 return result }
@After_Throwing printthrow(ctx, ex) { // ctx 为函数执行上下文 // ex 为错误信息 console.log('Loggy catch: '+ ex.message); console.log(ctx) }
@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
除了经过 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 Guide 后提交 Pull Request。