添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Core
Loader

加载机制 Loader

从概念上看,Loader 更加侧重的是以编程约束规范来提升项目协作一致性体验,因此整体设计上不与请求模型中的 Context/Trigger/Middleware 做过深耦合。

Core 内置 Loader 实现,主要的环节包括:

  • FindPath 扫描目录/读取 Manifest
  • Require 加载模块并 attach 对应的 Meta
  • Mount 将对象池交付并按照其上的 Meta 挂载到 Context/Application or IOC Contianer
  • 其中,Manifest 用于描述目录、Path、MetaHandler 的关系,格式如下:

    目录扫描/读取 Manifest

    Core 中内置默认的目录扫描规范,优先级 CustomLoader -> Manifest -> 目录。

  • 以下路径属于内置于 Core 的加载部分
  • config: ${rootDir}/config/config.${env}.ts
  • plugin: ${rootDir}/config/plugin.${env}.ts
  • extend:
  • ${rootDir}/app/extend/application.ts
  • ${rootDir}/app/extend/context.ts
  • ${rootDir}/app/extend/request.ts
  • ${rootDir}/app/extend/helper.ts
  • customLoader : { directory : 'app/need_custom_loader' , inject : 'singleton' | 'execution' | 'transient' , // 是否加载框架和插件的目录 loadunit : false ,

    配置加载 Load Config

    对于配置文件 config.${env}.ts 的加载,将根据服务的环境变量做如下处理。

  • 默认约定配置文件为 config.default.ts ,默认环境变量 ARTUS_SERVER_ENV ,可以通过 hook 来定制相关的环境变量解析方式。
  • 根据环境变量 env 来加载对应 config.${env}.ts 配置文件
  • config.${env}.ts 将采用 deepmerge (opens in a new tab) 的方式进行合并,值得注意的是对于数组项的合并,框架采用了直接替换的方式,因为数组元素之间往往存在顺序的要求,直接合并可能会导致与预期不符的结果。例如 环境变量为 ARTUS_SERVER_ENV=prod config.prod.ts 将会覆盖并合并 config.default.ts
  • config 支持异步,可通过异步函数来实现更加复杂的配置。由于 Artus 默认采用单进程模型,需要增强远程配置获取的前置能力,以便放置类似的异步配置加载逻辑。常见的异步获取远程配置有密钥管理服务 KMS(Key Management Service), 通过密钥来保护后台应用配置文件。
  • 配置加载完毕后,需要 dump 出配置信息用于分析和排查
  • [TODO] 可以通过 setConfig API,在 config 加载完成后对 config 进行修改, 以便框架记录配置改动的操作位置,方便分析
  • // 以 ARTUS_SERVER_ENV=prod 为例
    // config.default.ts
    // 下面的 Config 可以是 Object 或 Async Function
    export default {
        configA: 'configA',
        mysql: {
          host: 'localhost',
          port: 3306,
          password: '123456'
    // config.prod.ts
    export default {
        configB: 'configB',
        mysql: {
          host: '10.12.13.14',
          port: 3306,
          password: 'asdfsadfcsadcasdfaasfdaf='
    // 合并覆盖后的配置
        "configA": "configA",
        "configB": "configB",
        "mysql": {
          "host": "10.12.13.14",
          "port": 3306,
          "password": "asdfsadfcsadcasdfaasfdaf="
    
    // config.default.ts
    // 支持使用函数及异步方式
    export default async (appInfo, appConfig) => {
      return {
        url: appConfig.host + 'xxx',
        asyncData: await Promise.resolve('async_data'),
        // kms 解密配置
        encryptedData: await kmsClient.decrypt('encrypted_text'),
    

    ARTUS_SERVER_ENV 默认值枚举值如下

    enum ARTUS_SERVER_ENV {
      DEV = 'development',
      PROD = 'production',
      DEFAULT = 'default',
    
  • default: 默认值,即没有环境变量时默认读取的配置文件名 config.default.ts
  • development: 本地开发环境,即 ARTUS_SERVER_ENV=development 时,将 config.development.tsconfig.default.ts 进行 deepmerge 操作
  • production: 某种意义上的生产环境,即 ARTUS_SERVER_ENV=production 时,将 config.production.tsconfig.default.ts 进行 deepmerge 操作
  • 模块/文件加载 Load Module / File

    通过上一环节的目录扫描与读取,Loader 将能够持有一份标准化的入口文件 Path 列表(统称为 Manifest),Loader 需要针对该部分文件进行加载行为:

  • 对于 JS / TS 文件,以模块的形式
  • 其中,模块应包括 Meta 信息用于最终消费环节(如:Scope),有以下形式可用:
  • JS 编程界面中通过预先配置的形式由上层框架指定
  • TS 编程界面使用上层框架实现对应的装饰器,调用 Core 提供的接口使用 Reflect attach 上述信息到 Class
  • 模块的加载应当兼容 CommonJS 和 ECMAScript Module
  • 对于 JSON 等元信息文件,利用 Manifest 或 Loader 配置中的解析规则处理
  • POC 如下:

    interface FileItemLoader {
        loadFile() {}
    class ModuleLoader implements FileItemLoader {
        loadFile (path): Promise<Module> {
            if (isESModule(path)) {
                // 存在异步问题,可能影响 Loader 最终 API
                return import(path);
            return require(path);
    class MetaFileLoader implements FileItemLoader {
        loadFile (path) {
            if (isJSON(path)) {
                return JSON.parse(fs.readFileSync(path));
            return null;
    
  • Meta 信息挂载(JS)
  • 按照目录约定的形式,Meta 由上层框架通过对 Loader 的预先配置指定
  • Meta 信息挂载(TypeScript Decorator)
  • // app/service/user.ts 用户代码
    @Service(ScopeEnum.Application)
    class UserService {
      getInfo() {
        return {
          hello: 'world',
    // decorator.ts 上层框架实现代码/Core 默认配置
    export const Service = (scope: ScopeEnum) => (target) => {
      attachReflectMetaData(target, {
        namespace: 'sercice',
        scope,
    

    基于 IoC 的挂载机制

  • Artus 中的 Core 实现底层基于 IoC(控制反转,Inversion of Control)实现,便于在上层框架中集成 DI(依赖注入,Dependency Injection)、AOP(面向切面编程,Aspect Oriented Programming)等特性
  • 实现原理为:
  • 在 Core 中按照 Scope 提供 IOC Container 作为基础对象池,Scope 包括
  • Singleton(单例生命周期,全局执行一次实例化)
  • Execution(执行生命周期,每次执行时实例化)
  • Transient(临时生命周期,每次使用时实例化)
  • Loader 及上层框架的 Cutsom Loader 应当在挂载环节中将加载的类对象
  • 把 Controller 和 Service 等类的实例化创建过程托管给容器
  • Config 配置内容托管给容器
  • 插件中扩展的属性等也托管给容器,用户能够在 IoC 模式下注入这些扩展的属性
  • 例如 Redis、MySQL Client
  • 希望兼容现有 Egg/Gulu 中 app/ctx 等数据模型的前提下,提供 IoC 的开发模式
  • 对于上层框架,暴露 IoC Container 的操作方法,用于实现 DI/AOP...
  • 对于既有应用迁移,提供 delegate 代理层,模拟传统 app/ctx 用法,代理层的 getter/setter 访问 IoC Container
  • get(identifier) { const clazz = this.registry.get(identifier); const instance = new clazz(); // TODO: 处理注入的属性 return instance; set(identifier, target) { this.registry.set(identifier, target); // 装饰器 inject(identifier) { return (target, properName, index) => {}; // 装饰器 injectable() { return (target) => { // identifier 同时注册class 本身和类名两种形式 this.set(target, target); this.set(target.name, target); module.exports = new Container();

    lib/loader/file_loader.js (opens in a new tab)

    lib/loader/mixin/controller.js (opens in a new tab)

    lib/loader/context_loader.js (opens in a new tab)

  • 以注入形式使用 IoC 托管的类实例(TypeScript)
  • // src/app/controller/home.ts
    import { Inject, Injectable } from '@artusjs/a-framework';
    import { AnyService } from '../service/user';
    @Injectable({
        key: 'anyClassA' // Optionial
        scope: ScopeEnum.Singleton,
        namespace: 'controller'
    export default class AnyClassA {
        // 以下异步钩子会需要类似 getAsync 的异步函数进行操作
        @Init()
        async onInit() {
            await init();
        @Destroy()
        async onDestroy() {
            await destroy();
        @Inject(AnyService, {
            // Optionial
        anyService: AnyService;
        async anyMethod() {
            return this.anyService.getInfo();
    
  • 以传统模型使用 IoC 托管的类实例(JavaScript/代理层)
  • 注:如有异步情况(getAsync)这种代理层可能不能完全向下兼容(需要 await)
  • 对于代理层不能完全处理的情况(如:异步)需要在分析(构建时)或启动(运行时)给出 warning 提示
  • // src/app/controller/home.js
    export default class AnyClassB {
      async anyMethod() {
        const anyClassA = ctx.service.anyClassA;