基於 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。