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

koatty_container

v1.9.4

Published

IOC Container for Koatty.

Downloads

697

Readme

koatty_container

Typescript中IOC容器的实现,支持DI(依赖注入)以及 AOP (切面编程)。参考Spring IOC的实现机制,用Typescript实现了一个IOC容器,在应用启动的时候,自动分类装载组件,并且根据依赖关系,注入相应的依赖。它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

Version npm npm Downloads

IOC容器

IoC全称Inversion of Control,直译为控制反转。不是什么技术,而是一种设计思想。在OO开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:

传统OO程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了:

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

听着比较难以理解是不是,我们来举例说明,我们假定一个在线书店,通过BookService获取书籍:

export class BookService {

  private config: DataConfig = new DataConfig();
  private dataSource: DataSource = new MysqlDataSource(config);
	
  protected constructor() {

  }

  public getBook(long bookId): Book {
      try {
          const conn = this.dataSource.getConnection();
          ...
          return book;
      } catch (err){
        throw Error("message");
      }
  }
}

为了从数据库查询书籍,BookService持有一个DataSource。为了实例化一个DataSource,又不得不实例化一个DataConfig。

现在,我们继续编写UserService获取用户:


export class UserService {

  private config: DataConfig = new DataConfig();
  private dataSource: DataSource = new MysqlDataSource(config);

  public getUser(userId: number):User {
      try {
          const conn = this.dataSource.getConnection();
          ...
          return user;
      } catch (err){
        throw Error("message");
      }
  }
}

因为UserService也需要访问数据库,因此,我们不得不也实例化一个DataSource。

在处理用户购买的CartController中,我们需要实例化UserService和BookService:


export class CartController extends {

  private bookService = new BookService();
  private userService = new UserService(); 

  ...
}

类似的,在购买历史HistoryController中,也需要实例化UserService和BookService:


export class HistoryController extends {

  private bookService = new BookService();
  private userService = new UserService(); 

  ...
}

上述每个组件都采用了一种简单的通过new创建实例并持有的方式。仔细观察,会发现以下缺点:

  • 实例化一个组件,要先实例化依赖的组件,强耦合

  • 每个组件都需要实例化一个依赖组件,没有复用

  • 很多组件需要销毁以便释放资源,例如DataSource,但如果该组件被多个组件共享,如何确保它的使用方都已经全部被销毁

  • 随着更多的组件被引入,需要共享的组件写起来会更困难,这些组件的依赖关系会越来越复杂

如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

因此,核心问题是:

  • 1、谁负责创建组件?
  • 2、谁负责根据依赖关系组装组件?
  • 3、销毁时,如何按依赖顺序正确销毁?

解决这一问题的核心方案就是IoC。

组件分类

根据组件的不同应用场景,Koatty把Bean分为 'COMPONENT' | 'CONTROLLER' | 'MIDDLEWARE' | 'SERVICE' 四种类型。

  • COMPONENT 扩展类、第三方类属于此类型,例如 Plugin,ORM持久层等

  • CONTROLLER 控制器类

  • MIDDLEWARE 中间件类

  • SERVICE 逻辑服务类

循环依赖

随着项目规模的扩大,很容易出现循环依赖。koatty_container解决循环依赖的思路是延迟加载。koatty_container在 app 上绑定了一个 appReady 事件,用于延迟加载产生循环依赖的bean, 在使用IOC的时候需要进行处理:

// 
app.emit("appReady");

注意:虽然延迟加载能够解决大部分场景下的循环依赖,但是在极端情况下仍然可能装配失败,解决方案:

1、尽量避免循环依赖,新增第三方公共类来解耦互相依赖的类

2、使用IOC容器获取类的原型(getClass),自行实例化

API

通过组件加载的Loader,在项目启动时,会自动分析并装配Bean,自动处理好Bean之间的依赖问题。IOC容器提供了一系列的API接口,方便注册以及获取装配好的Bean。

reg(target: T, options?: ObjectDefinitionOptions): T;

reg(identifier: string, target: T, options?: ObjectDefinitionOptions): T;

注册Bean到IOC容器。

  • target 类或者类的实例
  • identifier 别名,默认使用类名。如果自定义,从容器中获取也需要使用自定义别名
  • options Bean的配置,包含作用域、生命周期、类型等等

get(identifier: string, type?: CompomentType, args?: any[]): any;

从容器中获取Bean。

  • identifier 别名,默认使用类名。如果自定义,从容器中获取也需要使用自定义别名
  • type 'COMPONENT' | 'CONTROLLER' | 'MIDDLEWARE' | 'SERVICE' 四种类型。
  • args 构造方法入参,如果传入参数,获取的Bean默认生命周期为Prototype,否则为单例Singleton

getClass(identifier: string, type?: CompomentType): Function;

从容器中获取类的原型。

  • identifier 别名,默认使用类名。如果自定义,从容器中获取也需要使用自定义别名
  • type 'COMPONENT' | 'CONTROLLER' | 'MIDDLEWARE' | 'SERVICE' 四种类型。

getInsByClass(target: T, args?: any[]): T;

根据class类获取容器中的实例

  • target 类
  • args 构造方法入参,如果传入参数,获取的Bean默认生命周期为Prototype,否则为单例Singleton

AOP切面

Koatty基于IOC容器实现了一套切面编程机制,利用装饰器以及内置特殊方法,在bean装载到IOC容器内的时候,通过嵌套函数的原理进行封装,简单而且高效。

切点声明类型

通过@Before、@After、@BeforeEach、@AfterEach装饰器声明的切点

| 声明方式 | 依赖Aspect切面类 | 能否使用类作用域 | 入参依赖切点方法 | | ------------ | ---------------- | ---------------- | ---------------- | | 装饰器声明 | 依赖 | 不能 | 依赖 |

依赖Aspect切面类: 需要创建对应的Aspect切面类才能使用

能否使用类作用域: 能不能使用切点所在类的this指针

入参依赖切点方法: 装饰器声明切点所在方法的入参同切面共享

例如:

@Controller('/')
export class TestController extends BaseController {
  app: App;
  ctx: KoattyContext;

  @Autowired()
  protected TestService: TestService;
  
  @Before(TestAspect) //依赖TestAspect切面类, 能够获取path参数
  async test(path: string){

  }
}

创建切面类

使用koatty_cli进行创建:

koatty aspect test

自动生成的模板代码:

import { Aspect } from "koatty";
import { App } from '../App';

@Aspect()
export class TestAspect {
    app: App;

    run() {
        console.log('TestAspect');
    }
}

装饰器

类装饰器

| 装饰器名称 | 参数 | 说明 | 备注 | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------- | | @Aspect() | identifier 注册到IOC容器的标识,默认值为类名。 | 声明当前类是一个切面类。切面类在切点执行,切面类必须实现run方法供切点调用 | 仅用于切面类 | | @BeforeEach() | aopName 切点执行的切面类名 | 为当前类声明一个切面,在当前类每一个方法("constructor", "init", "__before", "__after"除外)执行之前执行切面类的run方法。 | | | @AfterEach() | aopName 切点执行的切面类名 | 为当前类声明一个切面,在当前每一个方法("constructor", "init", "__before", "__after"除外)执行之后执行切面类的run方法。 | | | | | | |

属性装饰器

| 装饰器名称 | 参数 | 说明 | 备注 | | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---- | | @Autowired() | identifier 注册到IOC容器的标识,默认值为类名 cType 注入bean的类型 constructArgs 注入bean构造方法入参。如果传递该参数,则返回request作用域的实例 isDelay 是否延迟加载。延迟加载主要是解决循环依赖问题 | 从IOC容器自动注入bean到当前类 || | @Values() | val 属性值, 值类型同属性类型一致 defaultValue 被定义时,当val值为undefined、null、NaN时取值defaultValue型 | val值可以是一个函数,取值函数结果 ||

方法装饰器

| 装饰器名称 | 参数 | 说明 | 备注 | | -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------- | | @Before(aopName: string) | aopName 切点执行的切面类名 | 为当前方法声明一个切面,在当前方法执行之前执行切面类的run方法。 | | | @After() | aopName 切点执行的切面类名 | 为当前方法声明一个切面,在当前方法执行之后执行切面类的run方法。 | |

参数装饰器

| 装饰器名称 | 参数 | 说明 | 备注 | | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---- | | @Inject() | paramName 构造方法入参名(形参) cType 注入bean的类型 | 该装饰器使用类构造方法入参来注入依赖, 如果和 @Autowired() 同时使用, 可能会覆盖autowired注入的相同属性 | 仅用于构造方法(constructor)的入参 |