vscode源码解析(二) - 依赖注入

本文详细解释了VSCode中依赖注入的工作原理,包括使用createDecorator创建装饰器来记录类间依赖关系,通过registerSingleton注册单例类及其对应关系,以及InstantiationService如何在实例化时实现依赖注入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说实话我自己看着不晕,就怕读者骂我,如果有啥不周到的欢迎私信我,立刻修改!

vscode的依赖注入重要的步骤
1.通过createDecorator方法创建装饰器,并通过装饰器确定类之间的依赖关系,记录依赖项
2.通过registerSingleton方法注册装饰器对应真正的类(即之后被实例化的类,装饰器本身是一个接口,无法直接实例化),构造出注册列表
3.根据依赖关系与注册列表实例化对象,并实现依赖注入

1.装饰器

要解释vscode的依赖注入,就离不开装饰器
装饰器是什么呢?简单来说就是两点
1.从执行时机上说,在程序启动后,代码会被预解析一遍,这时候正式代码还未执行,而装饰器函数则会在这个阶段被执行。
2.从作用上来说,装饰器函数中可以获取到当前被装饰的类的信息。
这样我们就可以在类被实例化前,通过装饰器知道每个类之间的依赖关系,为依赖注入做准备

我们拿CodeApplication举个例子,当我们在new CodeApplication之前,@ILogService这个装饰器函数就会被执行,装饰器函数的参数中本身就带有当前被修饰类CodeApplication的信息,我们这时候就可以确定他们之间的依赖关系,也就是CodeApplication依赖InstantiationService和LogService
src/vs/code/electron-main/app.ts

export class CodeApplication extends Disposable {
	constructor() {
		@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
		@ILogService private readonly logService: ILogService,
	}
}

当然去ts官网搞懂装饰器可能更好…

接下来我们详细说明vscode上装饰器的应用,vscode对于需要被依赖注入的类(大多数是xxxService命名的服务类),都会使用createDecorator方法去生成装饰器,下面你可以看到,这里再次强调,装饰器也是一个函数,你可以从下面return的id中看出,但是他是一个特殊的函数,他专门去处理当前被装饰的类
src/vs/platform/instantiation/common/instantiation.ts

export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
	// 装饰器会被缓存在一个Map数据结构中
	if (_util.serviceIds.has(serviceId)) {
		return _util.serviceIds.get(serviceId)!;
	}
	// 注意这里的id其实是个方法,在程序启动后,装饰器的这个方法会预先被执行
	const id = <any>function (target: Function, key: string, index: number): any {
		if (arguments.length !== 3) {
			throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
		}
		// 在这里,我们
		storeServiceDependency(id, target, index);
	};

	id.toString = () => serviceId;

	_util.serviceIds.set(serviceId, id);
	return id;
}

function storeServiceDependency(id: Function, target: Function, index: number): void {
	// 在被装饰的类上记录依赖项,依赖项会被存在一个列表中(_util.DI_DEPENDENCIES)
	// 之后我们实例化被装饰的类时,就会自动注入这些依赖项
	if ((target as any)[_util.DI_TARGET] === target) {
		(target as any)[_util.DI_DEPENDENCIES].push({ id, index });
	} else {
		(target as any)[_util.DI_DEPENDENCIES] = [{ id, index }];
		(target as any)[_util.DI_TARGET] = target;
	}
}

这里拿个例子说明下,比如NativeLifecycleService这个类,你能在类声明中看到3个依赖项,在创建装饰器阶段都会被记录下来
在这里插入图片描述
在这里插入图片描述

2.registerSingleton类注册

回到上面CodeApplication的例子,如果你在IDE中去进行语义跳转,会发现下面的装饰器,其实对应的是个接口,而不是一个具体的类。我们其实需要告诉依赖注入框架,这个装饰器对应的类是哪个,这样依赖注入框架也才能自动装配该类

export class CodeApplication extends Disposable {
	constructor() {
		@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
		@ILogService private readonly logService: ILogService,
	}
}

这里vscode使用registerSingleton方法,这个方法的内容也很简单,将装饰器和对应的类传进来,一起作为组合存入一个数组_registry即可,这里就确定了他们的对应关系,之后实例化的时候也能找到这个关系

export function registerSingleton<T, Services extends BrandedService[]>(id: ServiceIdentifier<T>, ctorOrDescriptor: { new(...services: Services): T } | SyncDescriptor<any>, supportsDelayedInstantiation?: boolean): void {
	if (!(ctorOrDescriptor instanceof SyncDescriptor)) {
		ctorOrDescriptor = new SyncDescriptor<T>(ctorOrDescriptor as new (...args: any[]) => T, [], supportsDelayedInstantiation);
	}

	_registry.push([id, ctorOrDescriptor]);
}

// 一个使用的例子
registerSingleton(ILanguageConfigurationService, LanguageConfigurationService);

当然你可能会疑惑,这些registerSingleton又是什么时候被执行的呢?
这里我们就要说到src/vs/workbench文件夹下的这几个文件了
workbench.web.main.ts -> web环境
workbench.sandbox.main ->. electron-sandbox环境
workbench.desktop.main.ts -> desktop环境
workbench.common.main.ts -> common环境
这些文件在初始化时候会优先被引入,然后这些文件又作为入口文件直接import当前环境需要的文件,这个阶段会将registerSingleton方法一起执行,注册这些服务,这个阶段实例化还没有开始

这个类的注册信息会在实例化服务初始化的时候作为参数传入,之后我们实例化服务的时候会用到

		const contributedServices = getSingletonServiceDescriptors();
		for (const [id, descriptor] of contributedServices) {
			// 根据_registry信息构造serviceCollection
			serviceCollection.set(id, descriptor);
		}
		// 作为参数初始化InstantiationService
		const instantiationService = new InstantiationService(serviceCollection, true);

当然,vscode注册类的方法不止这一个,只是为了方便说明选用最常见的registerSingleton

3.InstantiationService 实例化服务

我们已经通过装饰器确定了依赖关系,现在我们需要去实例化类,实例化的方法都在src/vs/platform/instantiation/common/instantiationService.ts文件中,其中最重要的方法是_createInstance
ctor就表示我们将要实例化的类,我们重点看如何实现依赖注入
方法内首先通过getServiceDependencies获取到当前要实例化的类的依赖,这个依赖关系就是我们前面通过装饰器获取到的依赖关系
然后通过_getOrCreateServiceInstance 根据方法 indentifer 拿到 _services 中注册的依赖项,如果没有则创建。还记得刚刚说的通过registerSingeleton构造的serviceCollection吗?_services就是它。通过这个信息我们可以知道装饰器对应要实例化的类是哪个
最后的new是少不了的了,只是我们将需要的依赖作为参数传入到了constructor构造函数中了,这样就实现了依赖反转

	private _createInstance<T>(ctor: any, args: any[] = [], _trace: Trace): T {

		// arguments defined by service decorators
		const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
		const serviceArgs: any[] = [];
		for (const dependency of serviceDependencies) {
			const service = this._getOrCreateServiceInstance(dependency.id, _trace);
			if (!service) {
				this._throwIfStrict(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`, false);
			}
			serviceArgs.push(service);
		}

		const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;

		// check for argument mismatches, adjust static args if needed
		if (args.length !== firstServiceArgPos) {
			console.trace(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);

			const delta = firstServiceArgPos - args.length;
			if (delta > 0) {
				args = args.concat(new Array(delta));
			} else {
				args = args.slice(0, firstServiceArgPos);
			}
		}

		// now create the instance
		return <T>new ctor(...[...args, ...serviceArgs]);
	}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值