跳到主要内容
版本:4.0.0 🚧

Application 和 Context

Midway 的应用会同时对外暴露不同协议,比如 Http,WebSocket 等等,这里每个协议对 Midway 来说都是由独立的组件提供的。

比如我们前面示例中的 @midwayjs/koa ,就是一个提供 Http 服务的组件,下面我们将以这个组件为例,来介绍内置对象。

每个使用的 Web 框架会提供自己独特的能力,这些独特的能力都会体现在各自的 上下文(Context)和 应用(Application)之上。

定义约定

为了简化使用,所有的暴露协议的组件会导出 上下文(Context)和 应用(Application)定义,我们都保持一致。即 ContextApplication

比如:

import { Application, Context } from '@midwayjs/koa';
import { Application, Context } from '@midwayjs/faas';
import { Application, Context } from '@midwayjs/web';
import { Application, Context } from '@midwayjs/express';

且非 Web 框架,我们也保持了一致。

import { Application, Context } from '@midwayjs/socketio';
import { Application, Context } from '@midwayjs/grpc';
import { Application, Context } from '@midwayjs/rabbitmq';

Application

Application 是某一个组件中的应用对象,在不同的组件中,可能有着不同的实现。Application 对象上会包含一些统一的方法,这些方法统一来自于 IMidwayApplication 定义。

import { Application } from '@midwayjs/koa';

获取方式

在所有被依赖注入容器管理的类中,都可以使用 @App() 装饰器来获取 当前最主要 的 Application。

比如:

import { App, Controller, Get } from '@midwayjs/core';
import { Application } from '@midwayjs/koa';

@Controller('/')
export class HomeController {

@App()
app: Application;

@Get('/')
async home() {
// this.app.getConfig()
// this.app.getEnv()
}
}

Main Application

Midway 应用对外暴露的协议是组件带来的,每个组件都会暴露自己协议对应的 Application 对象。

这就意味着在一个应用中会包含多个 Application,我们默认约定,在 src/configuration.ts 中第一个引入的 Application 即为 Main Application主要的 Application)。

比如,下面的 koa 中的 Application 实例即为 Main Application主要的 Application)。

// src/configuration.ts

import { Configuration, ILifeCycle } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as ws from '@midwayjs/ws';

@Configuration({
imports: [koa, ws]
})
export class MainConfiguration implements ILifeCycle {
// ...
}

事实上 Application 都实现与 IMidwayApplication 接口,如果使用通用的 API,没有差别。

成为 Main Application 稍微有一些优势:

  • 在大部分的场景下,使用 @App() 即可注入获取,无需其他参数
  • 优先初始化

比如在多个导出 Application 组件需要加载中间件的情况下,可以简单的编码。

// src/configuration.ts

import { Configuration, ILifeCycle } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as ws from '@midwayjs/ws';

@Configuration({
imports: [koa, ws]
})
export class MainConfiguration implements ILifeCycle {
@App()
koaApp: koa.Application;

@App('webSocket')
wsApp: ws.Application;

async onReady() {
this.koaApp.useMiddleweare(...);
this.wsApp.useMiddleweare(...);
}
}

非主要的 Application,需要通过 @App() 装饰器的参数或者 ApplicationManager 来获取。

@App() 装饰器的参数为组件的 namespace

常见的 namespace 如下:

PackageNamespace
@midwayjs/webegg
@midwayjs/koakoa
@midwayjs/expressexpress
@midwayjs/grpcgRPC
@midwayjs/wswebSocket
@midwayjs/socketiosocketIO
@midwayjs/faasfaas
@midwayjs/kafkakafka
@midwayjs/rabbitmqrabbitMQ
@midwayjs/bullbull

getAppDir

用于获取项目根目录路径。

this.app.getAppDir();
// => /my_project

getBaseDir

用于获取项目 TypeScript 基础路径,默认开发中为 src 目录,编译后为 dist 目录。

this.app.getBaseDir();
// => /my_project/src

getEnv

获取当前项目环境。

this.app.getEnv();
// => production

getApplicationContext

获取当前全局依赖注入容器。

this.app.getApplicationContext();

getConfig

获取配置。

// 获取所有配置
this.app.getConfig();
// 获取特定 key 配置
this.app.getConfig('koa');
// 获取多级配置
this.app.getConfig('midwayLoggers.default.dir');

getLogger

获取某个 Logger,不传参数,默认返回 appLogger。

this.app.getLogger();
// => app logger
this.app.getLogger('custom');
// => custom logger

getCoreLogger

获取 Core Logger。

this.app.getCoreLogger();

getProjectName

获取项目名,一般从 package.json 中获取。

setAttr & getAttr

直接在 Application 上挂载一个对象会导致定义和维护的困难。

在大多数情况下,用户需要的是临时的全局数据存储的方式,比如在一个应用或者组件内部跨文件临时存取一个数据,从一个类保存,另一个类获取。

为此 Midway 提供了一个全局数据存取的 API,解决这类需求。

this.app.setAttr('abc', {
a: 1,
b: 2,
});

在另一个地方获取即可。

const value = this.app.getAttr('abc');
console.log(value);
// { a: 1, b: 2 }

getNamespace

通过 getNamespace API ,可以获取到当前 app 归属的组件的 框架的类型(即组件的 namespace)。

比如在 koa 组件中。

this.app.getNamespace();
// 'koa'

Context

Context 是一个请求级别的对象,在每一次收到用户请求时,框架会实例化一个 Context 对象,

在 Http 场景中,这个对象封装了这次用户请求的信息,或者其他获取请求参数,设置响应信息的方法,在 WebSocket,Rabbitmq 等场景中,Context 也有各自的属性,以框架的定义为准。

下面的 API 是每个上下文实现通用的属性或者接口。

获取方式

在 **默认的请求作用域 **中,也就是说在 控制器(Controller)或者普通的服务(Service)中,我们可以使用 @Inject 来注入对应的实例。

比如可以这样获取到对应的 ctx 实例。

import { Inject, Controller, Get } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';

@Controller('/')
export class HomeController {

@Inject()
ctx: Context;

@Get('/')
async home() {
// ...
}
}

由于 ctx 是一个框架内置 ctx 实例关键字,如果你希望用不同的属性名,也可以通过修改装饰器参数。

import { Inject, Controller, Get } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';

@Controller('/')
export class HomeController {

@Inject('ctx')
customContextName: Context;

@Get('/')
async home() {
// ...
}
}

如果一个服务可以被多个上层框架调用,由于不同框架提供的 ctx 类型不同,可以通过类型组合来解决。

import { Inject, Controller, Get } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { Context as BullContext } from '@midwayjs/bull';

@Provide()
export class UserService {

@Inject()
ctx: Context & BullContext;

async getUser() {
// ...
}
}

除了显式声明外,在拦截器或者装饰器设计的时候,由于我们无法得知用户是否写了 ctx 属性,还可以通过内置的 REQUEST_OBJ_CTX_KEY 字段来获取。

比如:

import { Inject, Controller, Get, REQUEST_OBJ_CTX_KEY } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';

@Controller('/')
export class HomeController {

@Inject()
ctx: Context;

@Get('/')
async home() {
ctx.logger.info(this.ctx === this[REQUEST_OBJ_CTX_KEY]);
// => true
}
}

requestContext

Midway 会为每个 Context 挂载一个 requestContext 属性,即请求作用域下的依赖注入容器,用来创建请求作用域下的对象。

const userService = await this.ctx.requestContext.getAsync(UserService);
// ...

logger

请求作用域下的默认 logger 对象,包含上下文数据。

this.ctx.logger.info('xxxx');

startTime

上下文执行开始的时间。

this.ctx.startTime
// 1642820640502

setAttr & getAttr

app 上的方法相同,这些方法的数据是保存在请求链路中,随着请求销毁,你可以在其中放一些请求的临时数据。

this.ctx.setAttr('abc', {
a: 1,
b: 2,
});

在另一个地方获取即可。

const value = this.ctx.getAttr('abc');
console.log(value);
// { a: 1, b: 2 }

getLogger

获取某个自定义 Logger 对应的上下文日志。

this.ctx.getLogger('custom');
// => custom logger

getApp

从 ctx 上获取当前框架类型的 app 对象。

const app = this.ctx.getApp();
// app.getConfig();