- Notifications
You must be signed in to change notification settings - Fork2
Core of rockerjs, the solution for Node EE. Rockerjs-core provides a IoC container and AOP based on TypeScript, Compatible with any system or framework.
License
weidian-inc/rockerjs-core
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
基于 TypeScript 和注解的轻量级IoC容器,提供了依赖注入、面向切面编程及异常处理等功能。Rockerjs Core可在任意工程中引入,是一个框架无关的IoC容器。
@rockerjs/core模块不依赖于任何框架,并与现有框架、库、类等保持兼容。通过DI(Dependency Injection)实现代码解耦和依赖解耦,在构建复杂应用时保证可扩展性与灵活性;同时提供二维编程的能力,基于注解可在各个连接点(Advice)进行非核心业务的操作,减少代码冗余;最后,它提供一种基于注解配置的简易异常处理机制 --Clamp机制,通过特定规则匹配异常处理程序实现处理。
安装
npm install @rockerjs/core
@rockerjs/core最佳实践需要结合TypeScript的装饰器一起使用(也可使用接口),因此需要在项目根目录添加tsconfig.json 文件,并配置编译选项 “experimentalDecorators”和“emitDecoratorMetadata”为 true,并推荐编译为ES6语法
示例 1
import{Container,Inject}from'@rockerjs/core';classUser{id:string="testId";name:string="testName";}classUserService{getUser(_id:string):User{returnnewUser();}}@InjectclassControlDefault{ @InjectuserService:UserService;test(){letuser:User=this.userService.getUser("test");console.log(user);}}@Inject('controllor-with-args',newDate())classControlDefaultWithArgs{name:string;time:Date;constructor(name:string,time:Date){this.name=name;this.time=time;} @InjectuserService:UserService;test(){letuser:User=this.userService.getUser("test");console.log(user,this.name,this.time);}}@Inject('controllor1','util',newDate())classControl{name:string;time:Date;constructor(name:string,time:Date){this.name=name;this.time=time;} @InjectuserService:UserService;test(){letuser: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实现letRPC={config:function(cfg:{serviceUrl:string,interfaces:Function[]}){if(cfg.interfaces){cfg.interfaces.forEach((type:FunctionConstructor)=>{if(type.prototype){letnewObj={},proto=type.prototype;letnms=Object.getOwnPropertyNames(proto);if(nms){nms.forEach((nm)=>{if(nm!='constructor'&&typeof(proto[nm])==='function'){newObj[nm]=function(){//{nm:方法名,arguments:参数表},改为调用远程请求过程returnarguments[0];//test return}}})}Container.provides([type,()=>{returnnewObj;}])}})}}}//--DEMO--------------------------------------------------------//1. 接口声明(注意,此处只能使用Concrete class)classProduct{getById(id:string):string{returnnull;}}//2. 应用RPC FrameworkRPC.config({serviceUrl:null,interfaces:[Product]//提供接口描述,在RPC中构建factory})//3. Service class@InjectclassService{ @Injectproduct:Product;test(){letid:string='tid';letrst=this.product.getById(id);console.log(rst);}}//4.测试Container.getObject<Service>('service').test();
提供了注解@Inject
来实现依赖的注入,当我们有如下GetDubboData
类时
classGetDubboData{p0:number;constructor(p0:number,p1:string){this.p0=p0;}}
我们可以通过以下方式实例化这个类,同时传入指定的参数
直接传递构造函数的参数
classSomeControl{ @Inject(1,'aaa')privatedubbo:GetDubboData}
给出构造函数的工厂函数
classSomeControl{ @Inject(function(){return[1,'aaa']})privatedubbo:GetDubboData}
无构造函数或参数为空
classSomeControl{ @Injectprivatedubbo:GetDubboData}
默认的实例化方法可以满足开发者的大部分需求,Rockerjs Core 提供了 provides 方法自定义实例化工厂,同时提供了获取类和类实例化函数映射表的方法。
直接传入类或工厂函数
// 形式一:形如 Container.provides(class extends UserService{})Container.provides(classextendsUserService{getUser(id:string):User{console.log(1);returnnewUser();}});
传入类及类的工厂函数
// 形式二:形如 Container.provides([UserService,FactoryFunction])Container.provides([UserService,()=>{returnnewclassextendsUserService{getUser(id:string):User{console.log(2);returnnewUser();}}();}]);
返回一个构造函数-工厂方法映射表, 结构如下
constglobalGeneralProviders:Map<FunctionConstructor,Function>=newMap<FunctionConstructor,Function>();
Container.injectClazzManually
方法提供了直接实例化注册表中的类的功能,参数为构造函数以及想要传入的参数
classSomeControl{transGet:GetTransData=Container.injectClazzManually(GetTransData,1,2);asyncgetProduct(_productId?:number){letjson:any=awaitthis.transGet.getDetail(_productId);console.log(json);}}
假设我们有一个获取异步数据的抽象类
abstractclassGetTransData{p0:numberconstructor(p0:number,p1:string){console.log(p0+p1)this.p0=p0}abstractasyncgetDetail(_proId:number):Promise<string>;}
可以通过 Container 的provides
API 来指定对应类型的工厂函数
Container.provides([GetTransData,(_p0,_p1)=>{returnnewclassextendsGetTransData{constructor(p0:number,p1:string){super(p0,p1);}asyncgetDetail(_id:number):Promise<string>{await((ms)=>newPromise(res=>setTimeout(res,ms)))(100)return`Hello${this.p0}`}}(_p0,_p1);}]);
最终通过@Inject
方法注入在测试类里面实例化这个对象
@InjectclassSomeControl{ @Inject(666,2)transGet:GetTransData;asyncgetProduct(_productId?:number){letjson:any=awaitthis.transGet.getDetail(_productId);console.log(json);}}Container.getObject<SomeControl>('someControl').getProduct();
得到输出结果
668Hello 666
面向切面编程(AOP是Aspect Oriented Program的首字母缩写)是指在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。Rockerjs Core 提供了 AOP 编程能力
假如我们想在下面的foo
方法执行前后打点
classTest{foo(){console.log('foo')}}newTest().foo()
我们可以声明一个日志类,通过@Aspect
注解声明其为一个切面类,通过@Pointcut
注解配置想要匹配的类、方法以及需要执行的钩子, 最后通过@Before
和@After
等注解标识被修饰方法在处于对应生命周期时需要执行的方法
import{Aspect,Pointcut,Before,After}from"@rockerjs/core";@AspectclassLogger{// 必须在静态方法上注册切点 @Pointcut({clazz:Test,// 定位被修饰的类scope:"prototype",// 默认只针对实例方法做Advice,配置“static”可针对静态方法Advicerules:".*foo.*",// 通过正则匹配到对应的方法,不填则匹配所有函数advices:["before:printStart","after"]// 过滤将要执行的钩子 (可细致到函数名)})staticmain(){}// 在执行被打点方法前执行的方法 @BeforeprintStart(){console.log("log:start:",newDate());}// 可以指定多个方法 @BeforeprintStart2(){console.log("log:start:",newDate());}// 在执行被打点方法后执行的方法 @AfterprintEnd(){console.log("log:end:",newDate());}}
必须在切面类的静态方法上注册切点
Rockerjs Core 提供了Before
,After
,After_Throwing
,After_Returning
,Around
等生命周期
- Before:在被打点函数执行前执行
- After:在被打点函数执行后执行
- After_Throwing:在被打点函数抛出异常时执行
- After_Returning:在被打点函数返回结果后执行
- Around:在被打点函数执行前后执行,类似于 koa 中间件
- 在 after 后执行
- 如果原生函数没有 return 任何东西则不执行
- 可以修改返回结果
@After_ReturningprintReturn(ctx,result){// ctx 为函数执行上下文// result 为函数执行的结果result+=666returnresult}
@After_Throwingprintthrow(ctx,ex){// ctx 为函数执行上下文// ex 为错误信息console.log('Loggy catch: '+ex.message);console.log(ctx)}
@AroundcurrentTime2(ctx,next){// ctx 为函数执行上下文// next 为匹配到的函数console.log('before',Date.now());letret=next();console.log('after',Date.now(),ret);returnret;}
我们可以为某个类同时注册多个切面类,再通过composeAspects
方法将它们组合起来,默认按照声明的顺序来包裹被打点的函数,最后声明的类会包裹在最外面一层
@Aspect()classLogger{// ...}@Aspect()classLogger2{ @Pointcut({clazz:Test,advices:["before","after","around",'after_returning']})staticmain(){} @BeforeprintStart(){console.log("2:start");} @Afterprintafter(){console.log("2:after");} @After_ReturningprintReturn(ctx,result){console.log('2:after_returning',result)returnresult+2} @AroundprintAround2(ctx,next){console.log("2:around:before");letret=next();console.log("2:around:after",ret);returnret;}}@Aspect()classLogger3{// ...}composeAspects({clazz:Test,// rules: '.*foo.*',aspects:[Logger,Logger2,Logger3]});
执行结果如下:
3:start2:start1:start3:around:before2:around:before1:around:beforefoo1:around:after bar2:around:after bar3:around:after bar1:after2:after3:after1:after_returning bar2:after_returning bar3:after_returning bar
如果想自定义切面之间执行的顺序,可以在切面注解上传入切面的次序(数值小的在洋葱模型的外层):
@Aspect({order:2})classLogger{}@Aspect({order:1})classLogger2{}@Aspect({order:3})classLogger3{}composeAspects({clazz:Test,aspects:[Logger,Logger2,Logger3]});
执行顺序如下:
2:start1:start3:start2:around:before1:around:before3:around:beforefoo3:around:after bar1:around:after bar2:around:after bar3:end1:end2:end
除了通过 Rockerjs Core AOP 中的 @After_Throwing
注解来实现错误捕获,我们还提供了更简便的实现错误捕获的方法,如下例,我们先声明一个错误捕获夹,然后在被包裹的函数上使用这个错误捕获夹,当函数执行过程中有异常发生时,我们能在捕获夹的 catch 方法中拿到错误信息以及函数执行的上下文。
import{Container,Inject,Catch,Clamp,ExceptionClamp}from"@rockerjs/core";// 1. 声明一个捕获器,实现 catch 方法@ClampclassClamperextendsExceptionClamp{catch(ex,ctx){console.log("hahaha: ****",ex,ctx);}}@InjectclassTest{// 2. 使用捕获器 @Catch("Clamper")test(){thrownewError("12322");}}Container.getObject<Test>('test').test();
与@After_Throwing
同时使用时,@Catch
会先捕获到错误,再次将错误抛出,@After_Throwing
才捕获到错误
@ClampclassClamperextendsExceptionClamp{catch(ex,ctx){console.log("hahaha: ****",ex,ctx);throwex// 将错误二次抛出}}@InjectclassTest{ @Catch("Clamper")test(){thrownewError("12322");}}@AspectclassExceptionClamp2{ @Pointcut({clazz:Test,advices:['after_throwing']})staticmain(){} @After_ThrowingprintThrow(ctx,ex){console.log('Loggy catch: '+ex.message);console.log(ctx)}}Container.getObject<Test>('test').test();
请参考Contribute Guide 后提交 Pull Request。
MIT
About
Core of rockerjs, the solution for Node EE. Rockerjs-core provides a IoC container and AOP based on TypeScript, Compatible with any system or framework.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.