Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

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

NotificationsYou must be signed in to change notification settings

weidian-inc/rockerjs-core

Repository files navigation

Build StatusCoverage Statusnpm packageLicense: MIT

基于 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

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

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

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

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

    classSomeControl{    @Inject(1,'aaa')privatedubbo:GetDubboData}
  2. 给出构造函数的工厂函数

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

    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();}}();}]);

获取实例化方法注册表

getGeneralHashmap()

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

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

面向切面编程(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());}}

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

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_ReturningprintReturn(ctx,result){// ctx 为函数执行上下文// result 为函数执行的结果result+=666returnresult}

@After_Throwing

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

@Around

@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

四、异常处理 Exception

除了通过 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

请参考Contribute Guide 后提交 Pull Request。

License

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

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp