源码版本:5.0.0
Nest使用入口main.ts
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();
第一个对象NestFactory
,源码位置在根目录./packages/core/nest-factory.ts。
export const NestFactory = new NestFactoryStatic();
看源码知道,NestFactoryStatic没有构造器函数constructor
,所以接着看create
函数。
该函数使用了重载定义,直接复制最后一个。
初步了解到:
- 第一个参数是使用是传入的类似
angular
module的对象, - 第二个参数可以是设置参数,也可以是类似
express
的MVC,如官网提到的fastify,覆盖默认使用的express
的。 - 第三个参数就只能是设置参数
public async create(
module: any,
serverOrOptions?: any,
options?: NestApplicationOptions,
): Promise<
INestApplication & (INestExpressApplication | INestFastifyApplication)
> {
const isHttpServer = serverOrOptions && serverOrOptions.patch;
// tslint:disable-next-line:prefer-const
let [httpServer, appOptions] = isHttpServer
? [serverOrOptions, options]
: [ExpressFactory.create()/* 默认使用Express*/, serverOrOptions];
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
httpServer = this.applyExpressAdapter(httpServer);
this.applyLogger(appOptions);
await this.initialize(module, container, applicationConfig, httpServer);
return this.createNestInstance<NestApplication>(
new NestApplication(container, httpServer, applicationConfig, appOptions),
);
}
配置options先略过,直接看ExpressFactory.create()
; 返回的是new ExpressAdapter(express())
。作用很简单,只是重新转发同样名字的方法给express()
。
接着生成应用的配置对象applicationConfig
;
private globalPipes: PipeTransform<any>[] = [];
private globalFilters: ExceptionFilter[] = [];
private globalInterceptors: NestInterceptor[] = [];
private globalGuards: CanActivate[] = [];
private globalPrefix = '';
可以知道,我们全局配置的管道、过滤器等就储存到此对象中。
下一步生成容器container
;传入了配置对象
const container = new NestContainer(applicationConfig);
constructor只是保存了该配置,其它方法跳过。
下一步再次初始化http服务。
httpServer = this.applyExpressAdapter(httpServer);
若是从外部传入的MVC对象,必须经过ExpressAdapter再包装。从是否有getHttpServer方法去判断。
接着看是否要覆盖默认的日志输出对象。
this.applyLogger(options);
接着等待初始化
await this.initialize(module, container, applicationConfig);
private async initialize(
module,
container: NestContainer,
config = new ApplicationConfig(),
httpServer: HttpServer = null,
) {
const instanceLoader = new InstanceLoader(container);
const dependenciesScanner = new DependenciesScanner(
container,
new MetadataScanner(),
config,
);
container.setApplicationRef(httpServer);
try {
this.logger.log(messages.APPLICATION_START);
await ExceptionsZone.asyncRun(async () => {
await dependenciesScanner.scan(module);
await instanceLoader.createInstancesOfDependencies();
dependenciesScanner.applyApplicationProviders();
});
} catch (e) {
process.abort();
}
}
生成实例加载器和依赖扫描器。
接着容器与httpServer绑定。
接着使用内部异常处理器(为了统一异常处理)异步去运行最后的步骤。
- 扫描当前文件的依赖
public async scan(module: Type<any>) {
await this.scanForModules(module);
await this.scanModulesForDependencies();
this.container.bindGlobalScope();
}
scanForModules
;这去扫描及保存模块(包括imports进来的模块)
public async scanForModules(
module: Type<any> | DynamicModule,
scope: Type<any>[] = [],
) {
await this.storeModule(module, scope);
const modules = this.reflectMetadata(module, metadata.MODULES);
for (const innerModule of modules) {
await this.scanForModules(innerModule, [].concat(scope, module));
}
}
await this.storeModule(module, scope);
有使用到类似angular的forwardRef方法。
然后进入容器对象的addModule
方法里,模块编译器先编译模块。
await this.moduleCompiler.compile(
metatype,
scope,
);
先统一一下返回的格式,对于延期加载(metatype是Promise对象的,如使用forwardRef的,先等待加载)。然后判断是否是动态模块,而官网中动态模块的对象类似这样
{
module: DatabaseModule,
providers: providers,
exports: providers,
};
特别的动态模块是有独有的module对象,所以由此判断。
- 非动态模块,直接返回
{type: metatype}
- 是动态模块,返回
{ type/*module*/, dynamicMetadata/*其余对象*/ }
然后把type 、scope、dynamicMetadata
传入模块token工厂(有用到映射,后期返回再看),生成唯一标识。
最后返回模块解析器解析返回{ type, dynamicMetadata, token }
。
回到容器对象的addModule
方法里,往下走,根据token判断是否已保存。没有保存则创建一个Module
实例。
Module
对象能保存组件、注入的服务、路由、导出的对象及相关模块。创建时先保存核心的注入(模块的实例,模块的配置、映射器、MVC对象、外部的上下文生成器、该模块的的所有模块)。
然后把Module
实例保存在容器的modules
对象中
接着对动态模块保存在dynamicModulesMetadata对象中,然后对动态模块里的模块(modules\imports)都执行下addModule
方法,共用一个作用域数组
接着判断是否是全局的模块。判断逻辑是去映射中找
!!Reflect.getMetadata(GLOBAL_MODULE_METADATA, metatype);
为什么可以这样了,其实到Global
的装饰器中看看就知道:
function Global(): ClassDecorator {
return (target: any) => {
Reflect.defineMetadata(GLOBAL_MODULE_METADATA, true, target);
};
}
是全局模块则添加到globalModules的Set对象中。
现在上面的storeModule
方法结束,
然后去映射中找外部imports进来的模块,要了解这一块,需要去看module
装饰器
export function Module(metadata: ModuleMetadata): ClassDecorator {
const propsKeys = Object.keys(metadata);
validateKeys(propsKeys);
showDeprecatedWarnings(metadata);
overrideModuleMetadata(metadata);
return (target: object) => {
for (const property in metadata) {
if (metadata.hasOwnProperty(property)) {
Reflect.defineMetadata(property, metadata[property], target);
}
}
};
}
function overrideModuleMetadata(moduleMetadata: ModuleMetadata) {
moduleMetadata.modules = moduleMetadata.imports
? moduleMetadata.imports
: moduleMetadata.modules;
moduleMetadata.components = moduleMetadata.providers
? moduleMetadata.providers
: moduleMetadata.components;
}
function showDeprecatedWarnings(moduleMetadata: ModuleMetadata) {
const modulesDeprecatedWarning =
'The "modules" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "imports" key instead.';
const componentsDeprecatetWarning =
'The "components" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "providers" key instead.';
moduleMetadata.modules && deprecate(modulesDeprecatedWarning);
moduleMetadata.components && deprecate(componentsDeprecatetWarning);
}
这里还能看出有在兼容以前的版本,并作警告。
外部imports的模块将都赋值到modules对象,最后开始映射,所以能从映射中找到imports的模块。
然后迭代imports的模块,再次运行scanForModules
函数。
现在scanForModules
运行结束,把模块都保存了。
接着扫描模块里的依赖项,作依赖注入的准备工作,其实就是处理Module
的配置项
await this.scanModulesForDependencies();
对获得map类型的modules,然后迭代
映射相关的modules,其实就是绑定模块里的外部模块与主模块的关系(对将废弃的modules也是兼容)。做法是先从映射中获得主模块里的外部imports模块,然后把动态模块(未必有)中的imports的模块也从容器的map类型
dynamicModulesMetadata
(之前addModule时有设置)获取到,把这些模块都组成新数组。然后迭代数组,将模块与taken绑定。能发现循环依赖的判断是迭代的值是undefined,还没实例化,这时用
forwardRef
就好了。最后调用容器的addRelatedModule
方法。addRelatedModule
方法: 先判读是否保存在modules对象中,没有则返回。有则获取出来(主模块),然后把获得的模块的作用域与父作用域组成一个新数组(加上父级作用域到最后,应该是为了让使用了SingleScope
的装饰器的模块在后面获取与之前一样的token值)。接着使用模块编译器获取token值,然后去modules对象读取对应的Module实例(被绑定的模块)。运行主模块的addRelatedModule方法绑定被绑定的模块
related
,逻辑就是保存在_relatedModules的Set类型对象中
然后到映射服务(ps:内部方法名没有改,但装饰器是providers对象导入了)
this.reflectComponents(metatype, token);
与上面的模块绑定一样,先组成一个数组,然后迭代,但这次是运行:
components.map(component => {
this.storeComponent(component, token);
this.reflectComponentMetadata(component, token);
this.reflectDynamicMetadata(component, token);
});
storeComponent
保存服务, 一开始的逻辑可以发现,区分自定义服务与普通服务是一个provide
属性,普通服务没有这个属性的。自定义服务需要根据配置去生成服务对象。先针对provide
属性,如果是内置的值(注册全局的拦截器、管道、守卫、过滤器),则需要再修改下provide的值,因为内置的是一个常亮值,使用Math.random().toString(36).substring(2, 32)
去获得一个随机值,去取代provide的值,这样的基本不会给用户注入的服务覆盖掉了,然后把改变后的值与之前的值及token值组成一个对象保存在applicationProvidersApplyMap
数组中,方便以后对照。最后都会运行容器的addComponent
.
return this.container.addComponent(component, token);
}
逻辑是由token获得module,然后运行
module.addComponent(component);
函数内同样要分情况处理:
- 非用户自定义的服务(无
provide
对象): 直接保存
this._components.set((component as Type<Injectable>).name, {
name: (component as Type<Injectable>).name,
metatype: component as Type<Injectable>,
instance: null,
isResolved: false,
});
- 用户自定义的服务,由
addCustomProvider
函数去添加,先由provide
获得name
,根据第三个属性去进行添加
- 对于有
useClass
属性的,就是把useClass赋值给metatype
collection.set(name, {
name,
metatype: useClass,
instance: null,
isResolved: false,
});
- 对于有
useValue
属性的:看出可以是Promise实例
collection.set(name, {
name,
metatype: null,
instance: value,
isResolved: true,
isNotMetatype: true,
async: value instanceof Promise,
});
- 对于有
useFactory
属性的:isNotMetatype应该是指不需要依赖注入,因为自带注入器对象了inject
const { provide, name, useFactory: factory, inject } = component;
collection.set(name, {
name,
metatype: factory as any,
instance: null,
isResolved: false,
inject: inject || [],
isNotMetatype: true,
});
最后容器的addComponent
返回服务的name
。
然后服务迭代的下一步:
this.reflectComponentMetadata(component, token);
这个的话是对使用WebSocketGateway
装饰器里配置的middleware
属性的值再次进行上面的storeComponent
然后服务迭代的最后一步:注入装饰器保存的对象,装饰器有(UseInterceptors
,````UseGuards,
UseFilters,
UsePipes```,及参数的【太多:Query()等等等等】)
对于非参数的装饰器,是使用如:
this.reflectInjectables(obj, token, GUARDS_METADATA);
- 先通过映射获得该类别所有的值
- 然后把服务的原型链上的所有方法(除最底层的Object的方法及所有构造器函数,设置了set或的get的也不要)组成一个数组。
- 然后去映射中看看上面的方法有没有也被该装饰器装饰了,有就,获得映射的值并过滤出来,赋值给
methodsInjectables
. - 然后因为装饰器传入的可能是一个数组,所以要转化成一维数组。
- 然后把类装饰数组与方法装饰数组都合成一个数组。
- 最后遍历最后的数组,运行
this.storeInjectable(injectable, token)
=>this.container.addInjectable(component, token);
=>module.addInjectable(injectable);
。最后的方法上面已经说明过。
而对于参数装饰器,因为也可以添加管道,所以需要获得所有参数装饰器中保存管道的pipes对象,然后运行上面的第六点。
this.reflectComponents(metatype, token);
结束
接着: 映射控制器
this.reflectControllers(metatype, token);
逻辑与上面映射服务的差不多,区别是最后遍历是
this.storeRoute(route, token);
this.reflectDynamicMetadata(route, token); / 这个与服务的一样。
storeRoute
: => this.container.addController(route, token);
=> module.addRoute(controller);
=>
this._routes.set(route.name, {
name: route.name,
metatype: route,
instance: null,
isResolved: false,
});
其实就是上面对于非自定义的服务的保存逻辑,因为控制器只能是一个Class,很好理解
this.reflectControllers(metatype, token);
结束
接着映射导出的对象:
this.reflectExports(metatype, token);
前面逻辑类似,也是看最后遍历:
this.storeExportedComponent(exportedComponent, token)
=>
this.container.addExportedComponent(exportedComponent, token);
=>
module.addExportedComponent(exportedComponent);
=>
public addExportedComponent(
exportedComponent: ComponentMetatype | string | DynamicModule,
) {
const addExportedUnit = (token: string) =>
this._exports.add(this.validateExportedProvider(token));
if (this.isCustomProvider(exportedComponent as any)) {
return this.addCustomExportedComponent(exportedComponent as any);
} else if (isString(exportedComponent)) {
return addExportedUnit(exportedComponent);
} else if (this.isDynamicModule(exportedComponent)) {
const { module } = exportedComponent;
return addExportedUnit(module.name);
}
addExportedUnit(exportedComponent.name);
}
根据类别去添加到this._exports
中;
最后添加前都要通过这个validateExportedProvider
去验证,逻辑就是this._components
有这个值,或者this._relatedModules
有这个值。否则就是没有注册的对象,抛出错误
this.reflectExports(metatype, token);
结束
此时使用模块装饰器配置的对象全部读取注册完毕。
所以await this.scanModulesForDependencies();
依赖注入的准备完成。
接着对所有模块绑定全局注册的模块
this.container.bindGlobalScope();
=>
public bindGlobalScope() {
this.modules.forEach(module => this.bindGlobalsToRelatedModules(module));
}
public bindGlobalsToRelatedModules(module: Module) {
this.globalModules.forEach(globalModule =>
this.bindGlobalModuleToModule(module, globalModule),
);
}
public bindGlobalModuleToModule(module: Module, globalModule: Module) {
if (module === globalModule) {
return undefined;
}
module.addRelatedModule(globalModule);
}
因为所有模块都已经注册完毕了,所以逻辑很简单。
此时,模块注册机依赖注入的准备工作已经完成。
NestFactory.initialize
里的await dependenciesScanner.scan(module);
完成。
接着创建依赖注入的实例
await instanceLoader.createInstancesOfDependencies();
=>
public async createInstancesOfDependencies() {
const modules = this.container.getModules();
this.createPrototypes(modules);
await this.createInstances(modules);
}
private createPrototypes(modules: Map<string, Module>) {
modules.forEach(module => {
this.createPrototypesOfComponents(module);
this.createPrototypesOfInjectables(module);
this.createPrototypesOfRoutes(module);
});
}
需要实例化的先原型实例化(构造器无参数),可能是可以优化速度
接着,正式开始实例化。
private async createInstances(modules: Map<string, Module>) {
await Promise.all(
[...modules.values()].map(async module => {
await this.createInstancesOfComponents(module);
await this.createInstancesOfInjectables(module);
await this.createInstancesOfRoutes(module);
const { name } = module.metatype;
this.logger.log(moduleInitMessage(name));
}),
);
}
第一步对模块使用的组件进行实例化
private async createInstancesOfComponents(module: Module) {
await Promise.all(
[...module.components.values()].map(
async wrapper =>
await this.injector.loadInstanceOfComponent(wrapper, module),
),
);
}
=>
public async loadInstanceOfComponent(
wrapper: InstanceWrapper<Injectable>,
module: Module,
) {
const components = module.components;
await this.loadInstance<Injectable>(wrapper, components, module);
}
loadInstance
: 首先判断是否是Promise,是则返回wrapper.done$
(其实这是从后面得出的逻辑,因为实例的获取是Promise的话,可以从此知道实例完成时间。)。
然后对包装对象wrappe的对象done$赋值promise,并取出resolve,方便后期运行promise的回调。
接着如果isResolved逻辑正的话,说明已实例话。结束方法。
否则,就要去获取构造器的参数去实例化wrapper。参数的注入可以在wrapper配置,但配置了的话会不再去映射中找design:paramtypes
的值(构造函数的元数据),获取元数据时,因为参数也可以配置装饰器,所有也有可能注入,所以去映射reflectSelfParams, 替换同样位置的元数据。
(ps: 为什么配置了通过这个值design:paramtypes
可以映射得到构造器的元数据呢,查看对应的装饰器(如:Injectable),是没有相关逻辑。其实这是typescript编译器帮忙做了,配置文件里:````"emitDecoratorMetadata": true,/有装饰器的自动保存目标的构造器的元数据在映射中,使用的key正是design:paramtypes/
"experimentalDecorators": true,/开启装饰器功能/```)
然后遍历参数(注入),如果参数需要实例化的,需要再次运行loadInstanceOfComponent去实例化
最后获得的参数,也会根据isResolved判断一下是否已经获取实例,逻辑负的话,运行callback去实例化,将实例赋值给currentMetatype.instance,把isResolved赋值true, 表明实例化完成,并调用上面done```知道实例完成的时间,不需要再运行一次,从而保证单例。
callback实例化逻辑,如果不是通过inject去配置的,则直接实例化,并覆盖原来实例
currentMetatype.instance = Object.assign(
currentMetatype.instance,
new metatype(...instances),
);
否则实例化方法有可能使Promise对象。需要这样:
const factoryResult = currentMetatype.metatype(...instances);
currentMetatype.instance = await this.resolveFactoryInstance(
factoryResult,
);
内部组件实例化完成,接着服务实例化:
await this.createInstancesOfInjectables(module);
逻辑与上面:只是loadInstance
的第二个参数为遍历module.injectables
的value();
接着控制器路由实例化逻辑与上面:只是loadInstance
的第二个参数为遍历module.routes
的value();
此时单个module里所有实例化完成,最后log打印信息。然后继续遍历。
NestFactory.initialize
里的await instanceLoader.createInstancesOfDependencies();
完成。
接着注册应用的(全局)的服务提供者,之前我们在map中保存的对应关系,通过moduleKey及providerKey找到实例,然后传入服务提供者的初始化函数,注册完毕。
到此NestFactory.initialize
完成。回到ceate
, 剩最后一步:
return this.createNestInstance<NestApplication>(
new NestApplication(container, httpServer, applicationConfig, appOptions),
);
把之前准备好的容器、MVC实例、应用配置、用户配置生成一个Nest应用实例。
关键:
new NestApplication(container, httpServer, applicationConfig, appOptions)
构造器:
constructor(
container: NestContainer,
private readonly httpAdapter: HttpServer,
private readonly config: ApplicationConfig,
private readonly appOptions: NestApplicationOptions = {},
) {
super(container, [], null);
this.applyOptions();/*查看配置,可以开启cors跨域*/
this.selectContextModule();/*获得container的第一个module,就是入口module*/
this.registerHttpServer();/*注册MVC成http服务或https服务*/
this.routesResolver = new RoutesResolver(this.container, this.config);/* 把控制器的路由注册到MVC上 */
}
new RoutesResolver(this.container, this.config);
=>
constructor(
private readonly container: NestContainer,
private readonly config: ApplicationConfig,
) {
this.routerExceptionsFilter = new RouterExceptionFilters(
container,
config,
container.getApplicationRef()/* 获得MVC对象 */,
);
this.routerBuilder = new RouterExplorer(
new MetadataScanner(),
this.container,
this.routerProxy,
this.routerExceptionsFilter,
this.config,
);
}
创建路由异常处理器和逻辑处理器(处理各种装饰器)。
然后运行createNestInstance方法,其实是新建一个代理去代理实例,禁止了写操作及读取原型链的属性,对于函数属性,用异常处理包装去运行。
目前入口const app = await NestFactory.create(ApplicationModule);
已完成。
最后这个
await app.listen(3000);
// nest-application.ts
public async listen(port: number | string, callback?: () => void);
public async listen(
port: number | string,
hostname: string,
callback?: () => void,
);
public async listen(port: number | string, ...args) {
!this.isInitialized && (await this.init());
this.httpServer.listen(port, ...args);
return this.httpServer;
}
关键逻辑在await this.init()
// nest-application.ts
public async init(): Promise<this> {
const useBodyParser =
this.appOptions && this.appOptions.bodyParser !== false;
useBodyParser && this.registerParserMiddleware();/* 首先判断是否要加载 BodyParser 中间件,个人觉得这个可以放到applyOptions里*/
await this.registerModules();
await this.registerRouter();
await this.callInitHook();
this.isInitialized = true;
this.logger.log(messages.APPLICATION_READY);
return this;
}
await this.registerModules();
可注册socket、微服务、和中间件
暂时只看中间件的注册, 这是准备工作,只是保存在中间件容器的对象中,非注册到MVC中:
// middleware-module.ts
public async resolveMiddleware(
middlewareContainer: MiddlewareContainer,
modules: Map<string, Module>,
) {
await Promise.all(
[...modules.entries()].map(async ([name, module]) => {
const instance = module.instance;
this.loadConfiguration(middlewareContainer, instance, name);
await this.resolver.resolveInstances(module, name);
}),
);
}
public loadConfiguration(
middlewareContainer: MiddlewareContainer,
instance: NestModule,
module: string,
) {
if (!instance.configure) return; /* 中间件是在实例原型链上的configure方面,没有的话,说明该模块没有配置中间件 */
const middlewareBuilder = new MiddlewareBuilder(this.routesMapper);
instance.configure(middlewareBuilder);
if (!(middlewareBuilder instanceof MiddlewareBuilder)) return; /* 放置configure函数修改此变量后报错 */
const config = middlewareBuilder.build();
middlewareContainer.addConfig(config, module);
}
中间件配置例子:
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('/cats');
}
}
从例子可以看到,需要传入MiddlewareConsumer
类型的实例,而MiddlewareBuilder
是以其为接口实现的。所以接下来看MiddlewareBuilder
的apply
函数
// packages\core\middleware\builder.ts
public apply(
...middleware: Array<Type<any> | Function | any>,
): MiddlewareConfigProxy {
return new MiddlewareBuilder.ConfigProxy(this, flatten(middleware));
}
中间件转成一维数组,然后使用代理,先对中间过滤非function
及把非Class
的转化为Class结构(方便后期实例化)。
// packages\core\middleware\builder.ts
public forRoutes(
...routes: Array<string | Type<any> | RouteInfo>,
): MiddlewareConsumer {
const {
middlewareCollection,
bindValuesToResolve,
routesMapper,
} = this.builder;
const forRoutes = this.mapRoutesToFlatList(
routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
);
const configuration = {
middleware: bindValuesToResolve(
this.includedRoutes,
this.contextParameters,
),
forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
};
middlewareCollection.add(configuration);
return this.builder;
}
先获得配置了中间间的路由信息(路径、http请求类型、回调方法(可空)、回调方法名(可空))
\\ packages\core\middleware\routes-mapper.ts
public mapRouteToRouteInfo(
route: Type<any> | RouteInfo | string,
): RouteInfo[] {
if (isString(route)) {
return [
{
path: this.validateRoutePath(route),
method: RequestMethod.ALL,
},
];
}
const routePath: string = Reflect.getMetadata(PATH_METADATA, route);
if (this.isRouteInfo(routePath, route)) {
return [
{
path: this.validateRoutePath(route.path),
method: route.method,
},
];
}
const paths = this.routerExplorer.scanForPaths(
Object.create(route),
route.prototype,
);
return paths.map(item => ({
path:
this.validateGlobalPath(routePath) + this.validateRoutePath(item.path),
method: item.requestMethod,
}));
}
从源码可以知道,forRoutes可以接受三种类型的
- 字符串类型,直接为路由路径,接受所有请求类型。返回
[
{
path: this.validateRoutePath(route),
method: RequestMethod.ALL,
},
];
- 是一个配置对象,非控制器,所以映射不到路由路径。返回:
[
{
path: this.validateRoutePath(route.path),
method: route.method,/* 可通过传入枚举类型的RequestMethod 去定义 */
},
];
- 是一个控制器,因为控制器可映射路径,里面的方法也可以映射二级路径,方法拦截的请求类型也可以映射,所以需要一一去获取。使用
routerExplorer
工具的scanForPaths
。获取逻辑是,从一级原型链中获得所有的非构造器的方法,然后一一去映射中获取拦截的请求类型及二级路径。返回二级路由信息对象数组,最后路由路径要与控制器映射的一级路径合并。
// packages\core\router\router-explorer.ts
public exploreMethodMetadata(
instance: Controller,
instancePrototype,
methodName: string,
): RoutePathProperties {
const targetCallback = instancePrototype[methodName];
const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);
if (isUndefined(routePath)) {
return null;
}
const requestMethod: RequestMethod = Reflect.getMetadata(
METHOD_METADATA,
targetCallback,
);
return {
path: this.validateRoutePath(routePath),
requestMethod,
targetCallback,
methodName,
};
}
接着组成路由的配置项,覆盖中间件的resolve
方法,添加可能从外部添加的参数,及把上面的路由信息去除设定中为排除在外的路径的信息。
然后把配置项添加到middlewareCollection。
回到loadConfiguration
const config = middlewareBuilder.build();
获取上面的配置信息。
然后调用nest-application.ts
的对象去添加配置
middlewareContainer.addConfig(config, module);
容器会分模块name保存中间件,及整体配置
回到middleware-module.ts
接着实例化中间件,也就是依赖注入
await this.registerRouter();
注册路由
public async registerRouter() {
await this.registerMiddleware(this.httpAdapter);
const prefix = this.config.getGlobalPrefix();
const basePath = prefix ? validatePath(prefix) : '';
this.routesResolver.resolve(this.httpAdapter, basePath);
}
第一步,把之前的中间件绑定对应的路由
// packages\core\middleware\middleware-module.ts
public async registerMiddleware(
middlewareContainer: MiddlewareContainer,
applicationRef: any,
) {
const configs = middlewareContainer.getConfigs();/* 获得中间件与路由的关系 */
const registerAllConfigs = (
module: string,
middlewareConfig: MiddlewareConfiguration[],
) =>
middlewareConfig.map(async (config: MiddlewareConfiguration) => {
await this.registerMiddlewareConfig(
middlewareContainer,
config,
module,
applicationRef,
);
});
await Promise.all(
[...configs.entries()].map(async ([module, moduleConfigs]) => {
await Promise.all(registerAllConfigs(module, [...moduleConfigs]));
}),
);
}
public async registerMiddlewareConfig(
middlewareContainer: MiddlewareContainer,
config: MiddlewareConfiguration,
module: string,
applicationRef: any,
) {
const { forRoutes } = config;
await Promise.all(
forRoutes.map(async (routeInfo: RouteInfo) => {
await this.registerRouteMiddleware(
middlewareContainer,
routeInfo,
config,
module,
applicationRef,
);
}),
);
}
public async registerRouteMiddleware(
middlewareContainer: MiddlewareContainer,
routeInfo: RouteInfo,
config: MiddlewareConfiguration,
module: string,
applicationRef: any,
) {
const middlewareCollection = [].concat(config.middleware);
await Promise.all(
middlewareCollection.map(async (metatype: Type<NestMiddleware>) => {
const collection = middlewareContainer.getMiddleware(module);
const middleware = collection.get(metatype.name);
if (isUndefined(middleware)) {
throw new RuntimeException();
}
const { instance } = middleware as MiddlewareWrapper;
await this.bindHandler(
instance,
metatype,
applicationRef,
routeInfo.method,
routeInfo.path,
);
}),
);
}
private async bindHandler(
instance: NestMiddleware,
metatype: Type<NestMiddleware>,
applicationRef: HttpServer,
method: RequestMethod,
path: string,
) {
if (isUndefined(instance.resolve)) {
throw new InvalidMiddlewareException(metatype.name);
}
const exceptionsHandler = this.routerExceptionFilter.create(
instance,
instance.resolve,
undefined,
);
const router = applicationRef.createMiddlewareFactory(method);/* 获得MVC的对应方法 */
const bindWithProxy = middlewareInstance =>
this.bindHandlerWithProxy(
exceptionsHandler,
router,
middlewareInstance,
path,
);
const resolve = instance.resolve();
if (!(resolve instanceof Promise)) {
bindWithProxy(resolve);
return;
}
const middleware = await resolve;
bindWithProxy(middleware);
}
private bindHandlerWithProxy(
exceptionsHandler: ExceptionsHandler,
router: (...args) => void,
middleware: (req, res, next) => void,
path: string,
) {
const proxy = this.routerProxy.createProxy(middleware, exceptionsHandler);/* 代理一样,为了处理异常 */
const prefix = this.config.getGlobalPrefix();
const basePath = prefix ? validatePath(prefix) : '';
router(basePath + path, proxy); /* 中间件注册成功 */
}
接着是注册路由主体了
this.routesResolver.resolve(this.httpAdapter, basePath);
// nest\packages\core\router\routes-resolver.ts
public resolve(appInstance, basePath: string) {
const modules = this.container.getModules();
modules.forEach(({ routes, metatype }, moduleName) => {
let path = metatype
? Reflect.getMetadata(MODULE_PATH, metatype)
: undefined;
path = path ? path + basePath : basePath;
this.registerRouters(routes, moduleName, path, appInstance);
});
this.registerNotFoundHandler();
this.registerExceptionHandler();
}
MODULE_PATH
应该是module上配置的前缀,但在源码上找不到对应设置的地方。
接着注册路由
public registerRouters(
routes: Map<string, InstanceWrapper<Controller>>,
moduleName: string,
basePath: string,
appInstance: HttpServer,
) {
routes.forEach(({ instance, metatype }) => {
// 根据Controller装饰器获得该controller实例的前缀
const path = this.routerBuilder.extractRouterPath(metatype, basePath);
const controllerName = metatype.name;
// 打印匹配信息
this.logger.log(controllerMappingMessage(controllerName, path));
this.routerBuilder.explore(
instance,
metatype,
moduleName,
appInstance,
path,
);
});
}
// nest\packages\core\router\router-explorer.ts
public explore(
instance: Controller,// 实例
metatype: Type<Controller>,// 构造器
module: string,// module名
appInstance,// MVC实例
basePath: string, // 前缀
) {
const routerPaths = this.scanForPaths(instance);
this.applyPathsToRouterProxy(
appInstance,
routerPaths,
instance,
module,
basePath,
);
}
// 从原型方法的装饰器中查找请求方法及路径,并返回关系对象
public scanForPaths(instance: Controller, prototype?): RoutePathProperties[] {
const instancePrototype = isUndefined(prototype)
? Object.getPrototypeOf(instance)
: prototype;
return this.metadataScanner.scanFromPrototype<
Controller,
RoutePathProperties
>(instance, instancePrototype, method =>
this.exploreMethodMetadata(instance, instancePrototype, method),
);
}
public applyPathsToRouterProxy(
router,
routePaths: RoutePathProperties[],
instance: Controller,
module: string,
basePath: string,
) {
(routePaths || []).map(pathProperties => {
const { path, requestMethod } = pathProperties;
this.applyCallbackToRouter(
router,
pathProperties,
instance,
module,
basePath,
);
// 打印次级路由匹配信息
this.logger.log(routeMappedMessage(path, requestMethod));
});
}
private applyCallbackToRouter(
router,
pathProperties: RoutePathProperties,
instance: Controller,
module: string,
basePath: string,
) {
const { path, requestMethod, targetCallback, methodName } = pathProperties;
const routerMethod = this.routerMethodFactory
.get(router, requestMethod)
.bind(router);
// 包装代理,为了处理异常及其它装饰器配置
const proxy = this.createCallbackProxy(
instance,
targetCallback,
methodName,
module,
requestMethod,
);
const stripSlash = str =>
str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
const fullPath = stripSlash(basePath) + path;
routerMethod(stripSlash(fullPath) || '/', proxy);// 注册成功
}
我们再看看包装代理里的
private createCallbackProxy(
instance: Controller,
callback: RouterProxyCallback,
methodName: string,
module: string,
requestMethod,
) {
const executionContext = this.executionContextCreator.create(
instance,
callback,
methodName,
module,
requestMethod,
);
const exceptionFilter = this.exceptionsFilter.create(
instance,
callback,
module,
);
return this.routerProxy.createProxy(executionContext, exceptionFilter);
}
executionContextCreator
为路由方法添加配置的管道、看守器、拦截器,及注入需要的参数。
// packages\core\router\router-execution-context.ts
public create(
instance: Controller,
callback: (...args) => any,
methodName: string,
module: string,
requestMethod: RequestMethod,
) {
const metadata = this.reflectCallbackMetadata(instance, methodName) || {}; // 获得参数装饰器的保存信息,如{"query:1":{index:0,data: 0} }(@Query())
const keys = Object.keys(metadata);
const argsLength = this.getArgumentsLength(keys, metadata); // 获得对象中最大的index+1值,就是参数的总数量
const pipes = this.pipesContextCreator.create(instance, callback, module); // 获得管道方法的数组,从下标0开始顺序是全局管道,控制器管道,方法管道
const paramtypes = this.reflectCallbackParamtypes(instance, methodName); // 获得原始参数类型
const guards = this.guardsContextCreator.create(instance, callback, module); // 获得看守器的数组
const interceptors = this.interceptorsContextCreator.create(
instance,
callback,
module,
);// 获得拦截器的数组
const httpCode = this.reflectHttpStatusCode(callback);获得需要返回的httpCode
// 下面展开说
const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module);
const isResponseHandled = paramsMetadata.some(
({ type }) =>
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
);// 判断是否是请求处理方法
const paramsOptions = this.mergeParamsMetatypes(paramsMetadata, paramtypes); // 是覆盖传入的对象
const httpStatusCode = httpCode
? httpCode
: this.responseController.getStatusByMethod(requestMethod);// 获得回复的httpCode
下面展开
const fnCanActivate = this.createGuardsFn(guards, instance, callback);
const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
const fnHandleResponse = this.createHandleResponseFn(
callback,
isResponseHandled,
httpStatusCode,
);
const handler = (args, req, res, next) => async () => {
fnApplyPipes && (await fnApplyPipes(args, req, res, next));
return callback.apply(instance, args);
};
public exchangeKeysForValues(
keys: string[],
metadata: RouteParamsMetadata,
moduleContext: string,
): ParamProperties[] {
this.pipesContextCreator.setModuleContext(moduleContext);
return keys.map(key => {
const { index, data, pipes: pipesCollection } = metadata[key];
const pipes = this.pipesContextCreator.createConcreteContext(
pipesCollection,
);
const type = this.mapParamType(key);
if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
const { factory } = metadata[key];
const customExtractValue = this.getCustomFactory(factory, data);
return { index, extractValue: customExtractValue, type, data, pipes };
}
const nType = Number(type);
const extractValue = (req, res, next) =>
this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next });
return { index, extractValue, type: nType, data, pipes };
});
}
上面是路由参数的转换,首先我们要了解参数装饰器的用法,如Query
export function Query();
export function Query(...pipes: (Type<PipeTransform> | PipeTransform)[]);
export function Query(
property: string,
...pipes: (Type<PipeTransform> | PipeTransform)[],
);
export function Query(
property?: string | (Type<PipeTransform> | PipeTransform),
...pipes: (Type<PipeTransform> | PipeTransform)[],
) {
return createPipesRouteParamDecorator(RouteParamtypes.QUERY)(
property,
...pipes,
);
}
直接使用可以获得request.query的对象,可装入query的对象名去获取此value,也可以注入管道对象对query进行转换。
OK,回到exchangeKeysForValues
方法里,
const pipes = this.pipesContextCreator.createConcreteContext(
pipesCollection,
);
获得管道的transform方法的数组。
const type = this.mapParamType(```RouteParamtypes```的枚举值);
获得属性名中除位置的字段()
if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
const { factory } = metadata[key]; // 拿到定义装饰器时传入的工厂函数
const customExtractValue = this.getCustomFactory(factory, data); // 把装饰器传入的属性加入工厂函数成一个新函数
return { index, extractValue: customExtractValue, type, data, pipes };
public getCustomFactory(factory: (...args) => void, data): (...args) => any {
return !isUndefined(factory) && isFunction(factory)
? (req, res, next) => factory(data, req)
: () => null;
}
这里是处理自定义的参数装饰器createParamDecorator
const nType = Number(type);
const extractValue = (req, res, next) =>
this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next }); // 通过枚举对应值,就可以获得返回的对象
return { index, extractValue, type: nType, data, pipes };
const fnCanActivate = this.createGuardsFn(guards, instance, callback);
这是生成守护者的总函数。
最后要返回的是这个函数
async (args: any[]) => {
const canActivate = await this.guardsConsumer.tryActivate(
guards,
args,
instance,
callback,
);
if (!canActivate) {
throw new ForbiddenException(FORBIDDEN_MESSAGE);
}
};
tryActivate
public async tryActivate(
guards: CanActivate[],
args: any[],
instance: Controller,
callback: (...args) => any,
): Promise<boolean> {
if (!guards || isEmpty(guards)) {
return true;
}
const context = this.createContext(args, instance, callback);生成传入的参数
for (const guard of guards) {
const result = guard.canActivate(context);
if (await this.pickResult(result)) {
continue;
}
return false;
}
return true;
}
所以在守护者使用中,我们可以通过context: ExecutionContext
获得MVC路由参数,控制器的构造器及处理方法
跟着的获得管道的总方法也差不多一样,只是还要把之前获得的管道与每个参数的管道合并一个数组,然后逐个去运行对参数进行转换。初始value就是从参数的extractValue方法去转换获得。
接下来是增加路由方法的返回响应的逻辑,因为我们写路由处理逻辑时是多数没有用到response
对象的,这里我们去增加处理返回响应。
const fnHandleResponse = this.createHandleResponseFn(
callback,
isResponseHandled,
httpStatusCode,
);
public createHandleResponseFn(
callback,
isResponseHandled: boolean,
httpStatusCode: number,
) {
const renderTemplate = this.reflectRenderTemplate(callback);// 获得映射中的渲染模板
const responseHeaders = this.reflectResponseHeaders(callback);// 获得映射中的响应头
if (renderTemplate) {
return async (result, res) => {
this.responseController.setHeaders(res, responseHeaders);
await this.responseController.render(result, res, renderTemplate);
};
}
return async (result, res) => {
this.responseController.setHeaders(res, responseHeaders);
!isResponseHandled &&
(await this.responseController.apply(result, res, httpStatusCode));
};
}
如果需要返回渲染的模板,则最后调用MVC的render方法,给模板传入的变量就是我们编写路由的返回值,就是result
。
没有模块的话,要判断是否是响应处理者(就是参数中是否有传入response或者next),有则只是设置下响应头,默认不作响应,即使我们没有使用到response
的响应方法及next()
,因为无法判断有没有用。所以传入了那些的话,要自己处理响应,不过官网好像也不推荐,只是为了兼容旧版本。
好了,现在有4个方法(守卫,管道,业务回调,响应方法)了,要按顺序组装。先守卫,然后管道处理参数,然后参数传入回调,最后响应方法。
const handler = (args, req, res, next) => async () => {
fnApplyPipes && (await fnApplyPipes(args, req, res, next));
return callback.apply(instance, args);
};
return async (req, res, next) => {
const args = this.createNullArray(argsLength);
fnCanActivate && (await fnCanActivate([req, res]));
const result = await this.interceptorsConsumer.intercept(
interceptors,
[req, res],
instance,
callback,
handler(args, req, res, next),
);
await fnHandleResponse(result, res); // 最后响应
};
但中间还有一个拦截器,若有拦截器,则需要将处理逻辑方法转换成 stream 流,来给拦截器去操作(加工,更改等),rxjs的强大操作符都可以使用了。
public async intercept(
interceptors: NestInterceptor[],
args: any[],
instance: Controller,
callback: (...args) => any,
next: () => Promise<any>,
): Promise<any> {
if (!interceptors || isEmpty(interceptors)) {
return await await next();
}
const context = this.createContext(args, instance, callback); // 与上面的守护一致
const start$ = defer(() => this.transformDeffered(next)); // defer操作符是订阅时才执行参数方法。所以可使用缓存策略,使得减少路由业务逻辑的频率。
const result$ = await interceptors.reduce(
async (stream$, interceptor) =>
await interceptor.intercept(context, await stream$),
Promise.resolve(start$),
); // 一层层数据流,可以知道,若拦截器不返回流,则无响应。
return await result$.toPromise();
}
此时正常路由处理的封装已完成,回到createCallbackProxy
\\ packages\core\router\router-explorer.ts
private createCallbackProxy(
instance: Controller,
callback: RouterProxyCallback,
methodName: string,
module: string,
requestMethod,
) {
const executionContext = this.executionContextCreator.create(
instance,
callback,
methodName,
module,
requestMethod,
);
const exceptionFilter = this.exceptionsFilter.create(
instance,
callback,
module,
);
return this.routerProxy.createProxy(executionContext, exceptionFilter);
}
还要封装异常过滤处理的方法。这个的create与上面的管道,守卫等差不多,也是先获得一个全局到控制器再到方法得过滤方法数组,只是数组内的成员不是方法,而是一个对象,除了有一个func
的方法外,还有一个exceptionMetatypes
的对象,这个是指拦截的错误类型。因为过滤器的装饰器是可以传参的,参数就是exceptionMetatypes。数组的获取逻辑如下
// packages\core\exceptions\base-exception-filter-context.ts
public createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
): R {
if (isUndefined(metadata) || isEmpty(metadata)) {
return [] as R;
}
return iterate(metadata)
.filter(
instance => instance && (isFunction(instance.catch) || instance.name),
)
.map(filter => this.getFilterInstance(filter))
.map(instance => ({
func: instance.catch.bind(instance),
exceptionMetatypes: this.reflectCatchExceptions(instance),
}))
.toArray() as R;
}
获取过滤数组后,若数组不为空,是要覆盖框架内的错误处理数组,还要先对数组反转,因为错误的抛出应该是从内到外的。
// packages\core\router\router-exception-filters.ts
public create(
instance: Controller,
callback: RouterProxyCallback,
module: string,
): ExceptionsHandler {
this.moduleContext = module;
const exceptionHandler = new ExceptionsHandler(this.applicationRef);
const filters = this.createContext(
instance,
callback,
EXCEPTION_FILTERS_METADATA,
);
if (isEmpty(filters)) {
return exceptionHandler;
}
exceptionHandler.setCustomFilters(filters.reverse());
return exceptionHandler;
}
异常过滤准备后了,要组合正常与过滤了。
// packages\core\router\router-proxy.ts
public createProxy(
targetCallback: RouterProxyCallback,
exceptionsHandler: ExceptionsHandler,
) {
return async (req, res, next) => {
try {
await targetCallback(req, res, next);
} catch (e) {
const host = new ExecutionContextHost([req, res]);
exceptionsHandler.next(e, host); // 这两参数就是过滤器的参数,此方法也只是从```filters```数组中find目标,去运行,找不到就使用内部的。
}
};
}
好了router-explorer的createCallbackProxy完成,回到applyCallbackToRouter,现在只剩下最终的绑定路由了
// packages\core\router\router-explorer.ts
private applyCallbackToRouter(
router,
pathProperties: RoutePathProperties,
instance: Controller,
module: string,
basePath: string,
) {
const { path, requestMethod, targetCallback, methodName } = pathProperties;
const routerMethod = this.routerMethodFactory
.get(router, requestMethod)
.bind(router);
const proxy = this.createCallbackProxy(
instance,
targetCallback,
methodName,
module,
requestMethod,
);
const stripSlash = str =>
str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
const fullPath = stripSlash(basePath) + path;
routerMethod(stripSlash(fullPath) || '/', proxy);
}
此方法完成后会打印路由关系日志。
设置的路由已完成注册。还要注册路由找不到时的处理方法,及非路由逻辑的异常处理方法。看下面的后面两步
// packages\core\router\routes-resolver.ts
public resolve(appInstance, basePath: string) {
const modules = this.container.getModules();
modules.forEach(({ routes, metatype }, moduleName) => {
let path = metatype
? Reflect.getMetadata(MODULE_PATH, metatype)
: undefined;
path = path ? path + basePath : basePath;
this.registerRouters(routes, moduleName, path, appInstance);
});
this.registerNotFoundHandler();
this.registerExceptionHandler();
}
(突然发现丢失了最后的部分,记得保存的了。)
后面两个异常处理简单说说,就是自己声明一个callback,然后调用app.use(callback)而已,一点都不绕。
最后调用各模块的生命周期函数onModuleInit。
基础nest应用启动完成!!