Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@rockerjs/common
Advanced tools
基于 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';
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
类时
class GetDubboData {
p0: number;
constructor(p0: number, p1: string) {
this.p0 = p0;
}
}
我们可以通过以下方式实例化这个类,同时传入指定的参数
直接传递构造函数的参数
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。
MIT
FAQs
Common modules and SPI for Rockerjs/mvc
We found that @rockerjs/common demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.