npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

objbox

v1.7.3

Published

基于typescript装饰器的轻量级IOC容器

Downloads

186

Readme

可以从npmjs.org 或 npmmirror.com 使用"npm install objbox"获取

github or npmjs 每个中版本的最后一个版本都是正式版,其余为测试版,例如有如下版本 1.2.1、1.2.2、1.2.3、1.3.1、1.3.2 ,其中只有1.2.3是正式版 小版本号表示bug修复,中版本表示有重要特性增加

简述

​ 为了减少typescript项目中逻辑与业务的依赖与高耦合,以及可以更方便对对象进行管理,故编写objbox(对象盒子)IOC容器。objbox是轻量级的,它支持文件扫描、class注册,对象注册、生命周期管理,自定义注解处理等。

​ objbox是基于typescript的装饰器的,你的项目需要支持typescript。当然,不是必须使用装饰器,只是推荐使用装饰器,且可以完成更多更强的功能

typescript配置

  • typescript配置:

    • {
      	"include": [
                  
      	],
      	"compilerOptions": {
      		"experimentalDecorators": true, //开启装饰器【必须】
      		"module": "commonjs", // commonjs 模式【必须】
      		"target": "ES6", // es6标准【至少满足es6】
              "strict": false, // 关闭严格模式【推荐】,不然会出现一些编译上的繁琐问题 
      		"outDir": "./out",
      		"paths": {},
      		"types": [
      		  "node"
      		]
      	}
      }

基础样例

import { ComponentCreatedType, ObjBoxHelper } from "objbox";
import { Level, LoggerManagerConfig, TimeFlag } from "objbox/libs";
import { DefaultApplicationHandler } from "objbox/demo/HandlerDemo/DefaultApplicationHandler"
import { DefaultComponentHandler } from "objbox/demo/HandlerDemo/DefaultComponentHandler"

function main() {
    // 配置容器日志(非必要)
    let loggerConfig: LoggerManagerConfig = {
        level: Level.ALL,
        timeFormate: `${TimeFlag.Year}-${TimeFlag.Month}-${TimeFlag.Day} ${TimeFlag.Hour}:${TimeFlag.Minute}:${TimeFlag.Second}`
    }

    let ob = ObjBoxHelper.newObjBox(loggerConfig);

    // 注册处理器(非必要)
    ob.registerFromClass(DefaultApplicationHandler)
    ob.registerFromClass(DefaultComponentHandler)


    // 方式1:从文件扫描与注册模板
    // ob.registerFromFiles([
    //     new ScanDir(__dirname + "/src")
    // ])

    //方式2:通过method注册
    ob.registerFromMethod(() => {
        return { a: 123 }
    }, "AA", ComponentCreatedType.Singleton)

    // 方式3:通过class注册
    class A {
        v: 123
    }
    ob.registerFromClass(A);

    //方式4:直接注册对象
    ob.registerByObject({ v: 123 }, "obj")

    // 启动装载
    ob.load()

    //启动容器应用
    ob.run();

    //获取注册的组件
    console.log(ob.getComponent("AA"))
    console.log(ob.getComponent("A"))
    console.log(ob.getComponent("obj"))

    /**
     * 更方便的基于文件扫描的注解方式请查看readme
     */
}
main()

原理说明

/**
 * 基于typescript与nodejs的轻量级IOC容器
 * 
 * ObjBox思想就是,一切皆组件(Component)
 * 一切围绕着如何创建组件、如何管理组件、如何执行组件三个方面进行
 * 思路是通过扫描(注册)class、method、Object构建模板template,将信息存储到模板上(包括创建的组件信息、创建方式、来源等)、
 * 随后在load阶段对模板进行模板遍历与生成组件,并触发对应的处理器与生命周期钩子
 * 
 * ObjBox内有3种基本概念:组件、模板、注解
 * 模板:指的是具有 @Component 注解标记且export的class
 * 组件:被实例化的模板
 * 注解:也就是 @xxx 叫做注解(支持class注解、method注解,property注解、methodArguments注解,注解信息存储在模板的prototype内,作为模板的元数据)
 * 他们的处理顺序为注解、模板、组件
 * 基础注解有:
    @ApplicationHandler 应用处理器
    @ComponentHandler 组件处理器
    @BeanComponent 创建Bean组件的组件(bean组件工厂)
    @Bean 通过method创建组件
    @Component 组件注解
    @AutowireProperty 通过属性注入
    @AutowireMethod 通过方法注入
 */

/**
    以文件注册为流程简述IOC处理流程
================== 预处理阶段 ==================
说明:预处理阶段是利用typescript的特性以及装饰器特性,进行元数据注入
    0、typescript编译以及被特定装饰器预处理

================== 注册阶段 ==================
说明:注册阶段可以向容器内注册模板,可以从文件、class、method等方式进行注册(或者多种方式并用也可以,只要在),这里以文件方式举例
    1、扫描组件文件,生成class的function
    2、验证function的prototype规范性
    3、通过function创建组件扫描模板ScannedTemplate
    4、校验模板是否为ApplicationHandler实例化并存储
    5、存储所有组件模板(如果ApplicationHandler被标注为Component,会成为单例组件)

================== 装载阶段 ==================
    ===== 装载前期 =====
    说明:实际上在装载中期之前,可以通过ApplicationHandler的start进行模板注册。不允许在注册阶段之后注册任何ApplicationHandler,会不断触发start导致死循环
    6、对所有模板触发 @ApplicationHandler 的 start(objBox)

    ===== 装载中期 =====
    7、对所有模板触发 @ApplicationHandler 的 preprocessScannedTemplate(objbox,sTemplates[])
    8、校验模板是否为 @ComponentHandler 实例化并存储
    9、校验模板是否为 @BeanComponent 实例化并创建 @Bean 的组件模板
    10、对新建的 @bean 模板触发 @ApplicationHandler 的 preprocessScannedTemplate(objbox,sTemplates[])

    11、对所有模板触发 @ComponentHandler 的 scaned(objbox,name,sTemplate)
    
    ===== 装载后期 =====
    12、触发应用处理器 @ApplicationHandler 的 processBeforePrepare(objbox)
    13、对所有模板创建第一个实例组件并进行依赖注入
          13.1、从模板单例实例化处获取实例,如果没有去缓存取
          13.2、如果缓存没有,新建
            13.3、新建 @Component 组件 ObjBox.createComponentFromTemplate(sTemplate)
            13.4、触发 @ComponentHandler 的 beforeCreated(objbox,sTemplate,component)
            13.5、触发 @TemplateHandler 的 created
            13.6、触发 @ComponentHandler 的 afterCreated(objbox,sTemplate,component)
            13.7、依赖注入 @Component 组件 objbox.injectComponentDependency(component)
            13.8、触发 @ComponentHandler 的 beforeCompleted(objbox,sTemplate,component)
            13.9、触发 @TemplateHandler 的 completed
            13.10、触发 @ComponentHandler 的 afterCompleted(objbox,sTemplate,component)
            13.11、如果应用已经运行
                13.11.1、触发 @ComponentHandler 的 beforeReady
                13.11.2、触发 @TemplateHandler 的 ready
                13.11.3、触发 @ComponentHandler 的 afterReady
    14、触发应用处理器 @ApplicationHandler 的 processAfterPrepare(objbox)
    
================== 运行阶段 ==================
说明:容器正式启动,触发所有组件的ready接口
    15、run启动程序
        15.1、触发 @ApplicationHandler 的 beforeRunning(objbox)
        15.2、触发 @ComponentHandler 的 beforeReady(objbox,sTemplate,component)
        15.3、触发 @TemplateHandler 的 ready
        15.4、触发 @ComponentHandler 的 afterReady(objbox,sTemplate,component)
        15.5、触发 @ApplicationHandler 的 afterRunning(objbox)
*/

注解方式组件

注解方式可以通过文件扫描,也可以通过import导入(推荐使用扫描,毕竟IOC容器目的就是减少依赖)

//main.ts

import { ObjBoxHelper, ScanDir } from "objbox";
import { Level, LoggerManagerConfig, TimeFlag } from "objbox/libs";
import { DefaultApplicationHandler } from "objbox/demo/HandlerDemo/DefaultApplicationHandler"
import { DefaultComponentHandler } from "objbox/demo/HandlerDemo/DefaultComponentHandler"
import * as fs_extra from 'fs-extra';

function main() {
    // 配置容器日志(非必要)
    let loggerConfig: LoggerManagerConfig = {
        level: Level.ALL,
        timeFormate: `${TimeFlag.Year}-${TimeFlag.Month}-${TimeFlag.Day} ${TimeFlag.Hour}:${TimeFlag.Minute}:${TimeFlag.Second}`
    }

    let ob = ObjBoxHelper.newObjBox(loggerConfig,fs_extra);

    // 注册处理器(非必要)
    ob.registerFromClass(DefaultApplicationHandler)
    ob.registerFromClass(DefaultComponentHandler)


    // 方式1:从文件扫描与注册模板
    ob.registerFromFiles([
    	new ScanDir(__dirname + "/src")
    ])

    // 启动装载
    ob.load()

    //启动容器应用
    ob.run();
}
main()

然后新建对应的目录src,目录结构如下

+--------
+-main.js
+-src
   +---------
   +-xxx.ts
   +...

并且在内部编写如下代码文件

//xxx.ts

import { AutowireMethod, AutowireProperty, Bean, BeanComponent, Component, ComponentCreatedType,TemplateHandler } from 'objbox';


// @Component()
// export class DefaultComponent implements TemplateHandler{
//     created(){
//         console.log("DefaultComponent-created")
//     };
//     completed(){
//         console.log("DefaultComponent-completed")
//     };
//     ready(){
//         console.log("DefaultComponent-ready")
//     };
// }


@Component("A")
export class A{

}
@Component()
export class B{

}
@BeanComponent()
export class MyBeanComponent{
    @Bean("C",ComponentCreatedType.Singleton)
    createC(){
        return {
            msg:"this is C"
        }
    }
}
@Component()
export class Main implements TemplateHandler{
    @AutowireProperty("A")
    a:A
    
    b:B
    @AutowireMethod("B")
    setB(b:B){
        this.b = b;
    }
    
    @AutowireProperty("C")
    c:any

    ready(){
        // console.log(this.a,this.b,this.c)
    }
}


@Component()
export class R1{
    @AutowireProperty("R2",false)
    r2:R2
}

@Component("R2")
export class R2{
    @AutowireProperty("R1")
    r1:R1
}

编译main.ts以及xxx.ts之后执行main.js即可。推荐如下配置tsconfig.json,将扫描的路径包含编译进去

{
    ...
    "include": [
        "main.ts",
		"./src/**/*"
	],
    ...
}

创建自定义注解

通过全局安装npm install -g objbox,还可以使用创建注解命令

objbox anno-class <name> <path>         //create an annotation of class in path
objbox anno-property <name> <path>      //create an annotation of property in path
objbox anno-method <name> <path>        //create an annotation of method in path
objbox anno-methodArg <name> <path>     //create an annotation of methodArg in path

使用注解

通过阅读objbox流程,可以在ApplicationHandler或ComponentHandler中使用ObjBoxHelper进行操作注解,以及操作原始数据,达到自定义注解效果

//main.ts
import { Component, ComponentHandler, ComponentHandlerInterface, ObjBoxHelper, ObjBoxInterface, ScanDir, ScannedTemplate, getFunName, registerMethod } from "objbox";
import { LoggerManagerConfig, TimeFlag, Level } from "objbox/libs";

/**
 * 默认方法注解模板
 * @param yourArg1 
 * @param yourArg2 
 */
export function Log(): MethodDecorator {
    //获取当前函数名称,等效于let _annotationName = "Log"
    let _annotationName = getFunName(2)

    //@ts-ignore
    return function (target: any, key: string, descriptor: PropertyDescriptor) {
        registerMethod<any>(_annotationName, {}, target, key, descriptor)
    }
}
@ComponentHandler()
class LogHandler implements ComponentHandlerInterface {
    scanned: (objbox: ObjBoxInterface, template: ScannedTemplate) => void;
    beforeCreated(objbox: ObjBoxInterface, template: ScannedTemplate, component: any) {
        let methodAnnos = ObjBoxHelper.getMethodsAnnotationFromComponent(Log.name, component)
        for (let methodAnno of methodAnnos) {
            ObjBoxHelper.insertFunctionBeforeMethod(component, methodAnno.methodName, (...args) => {
                console.log("args: ", ...args);
                return args
            })
            ObjBoxHelper.insertFunctionAfterMethod(component, methodAnno.methodName, (result: any) => {
                console.log("result: " + result)
                return result
            })
        }
    }
    beforeCompleted?: (objbox: ObjBoxInterface, template: ScannedTemplate, component: any) => void;
    beforeReady?: (objbox: ObjBoxInterface, template: ScannedTemplate, component: any) => void;
}



@Component()
class YourClass {
    @Log()
    add(num1: number, num2: number) {
        return num1 + num2
    }
}






function main() {
    // 配置容器日志(非必要)
    let loggerConfig: LoggerManagerConfig = {
        level: Level.ALL,
        timeFormate: `${TimeFlag.Year}-${TimeFlag.Month}-${TimeFlag.Day} ${TimeFlag.Hour}:${TimeFlag.Minute}:${TimeFlag.Second}`
    }

    let ob = ObjBoxHelper.newObjBox(loggerConfig);

    ob.registerFromClass(LogHandler)
    ob.registerFromClass(YourClass)

    // 启动装载
    ob.load()

    //启动容器应用
    ob.run();


    let yourclass: YourClass = ob.getComponent(YourClass.name)
    yourclass.add(123, 456);
}
main()