vscode源码学习6-注册服务

565 阅读5分钟

ioc 设计 ---- 注册服务

上次,我们讲到了「定义服务」,这里,我们开始解读注册服务部分。

注册的过程,其实就是将服务放入服务池里,vscode 里有两种方式放入,这里,我们讲最直接的,直接放入集合。

首先,我们需要一个服务集合:

const services = new ServiceCollection();
// serviceCollection

import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor } from './descriptors';

export class ServiceCollection {

	private _entries = new Map<ServiceIdentifier<any>, any>();

	constructor(...entries: [ServiceIdentifier<any>, any][]) {
		for (let [id, service] of entries) {
			this.set(id, service);
		}
	}

	set<T>(id: ServiceIdentifier<T>, instanceOrDescriptor: T | SyncDescriptor<T>): T | SyncDescriptor<T> {
		const result = this._entries.get(id);
		this._entries.set(id, instanceOrDescriptor);
		return result;
	}

	has(id: ServiceIdentifier<any>): boolean {
		return this._entries.has(id);
	}

	get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
		return this._entries.get(id);
	}
}

基于此处,我们要开始讲解:ServiceIdentifierSyncDescriptor 了。

ServiceIdentifier

/**
 * Identifies a service of type T
 */
export interface ServiceIdentifier<T> {
	(...args: any[]): void;
	type: T;
}

上面是一个ServiceIdentifier ,有点难看懂,没关系,我们结合下具体的使用,前面,我们有复制到如下内容:

export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {

	if (_util.serviceIds.has(serviceId)) {
		return _util.serviceIds.get(serviceId)!;
	}

	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, false);
	};

	id.toString = () => serviceId;

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

返回值是一个:ServiceIdentifier, 即 id 这个东东, 而我们又是按照如下方式使用的createDecorator

export const ITestService = createDecorator<ITestService>('testService');

所以,结合起来,我们看,id 本身是一个函数, 其实就是 ts的属性参数装饰器函数,并且重写了其toString方法,即 id 这个函数,在使用id.toString()时候,返回服务名:testService, 但我们看到,在使用ServiceIdentifier的时候,其实并没有明面上去定义type 这个字段,我们通过demo 来测试下。

export interface ServiceIdentifier<T> {
	(...args: any[]): void;
	type: T;
}
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
  const id = () => {
    console.log('hello, here is id');
  }
  id.toString = () => serviceId;
  return id;
}

interface ITestService {
	test: () => void;
}

class TestService implements ITestService {
  test() {
    console.log('hello');
  }
}

const id = createDecorator<ITestService>('hello');

id();

console.log('toString:', id.toString());
console.log('type:', id.type);

发现,如果我们不明确的去定义:type的话,会有 tslint报错,那vscode是怎么搞的。其实我们发现,在定义的时候:

const id = <any>function (target: Function, key: string, index: number): any 

这里,有any,其实就是转换成了 any 。我们也这样,然后测试一波。

export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
  const id = <any>function() {
    console.log('hello, here is id');
  }
  id.toString = () => serviceId;
  return id;
}
➜  my-vscode git:(feat-day-05) ✗ yarn test                                    
yarn run v1.22.4
warning package.json: No license field
$ ts-node ./src/test.ts
hello, here is id
toString: hello
type: undefined

额,我们发现,这个type不赋值就没东西的,算了,后面遇到的话再看是在哪里赋值的,先记个 TODO。

总结

ServiceIdentifier就是返回了一个typescript的参数装饰器函数,并且重写了 toString 方法,让其返回服务的名字即 id。

SyncDescriptor

我们在注册服务的时候,如果将所有的服务立即实例化,则在项目较大的时候,会有性能问题,因此,vscode 在这里有一个优化,就是延迟实例化,即一个思想:idle-until-urgent,除非立即需要,否则等待空闲时间再去实例化。因此,很多时候,我们不需要服务立即实例化,但我们也得先把服务注册上。因此,在服务集合里包含两种类型。

  • 服务实例本身,即被使用过的服务实例则会一直存在集合里。
  • 服务装饰器,可以看作是服务的一个占位,表示在集合里有这个服务若要用到此服务,有以下步骤:
    • 则根据装饰器描述的内容去把服务实例化
    • 将服务装饰器替换为服务实例
    • 后续再次调用的时候,则为服务实例本身

我们来看一下具体结构:

export class SyncDescriptor<T> {
	readonly ctor: any; // 服务的构造函数
	readonly staticArguments: any[]; // 服务初始化需要的静态参数
	readonly supportsDelayedInstantiation: boolean; // 是否允许此对象延迟实例化,这个属性目前还没理解怎么用的,先记个TODO

	constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) {
		this.ctor = ctor;
		this.staticArguments = staticArguments;
		this.supportsDelayedInstantiation = supportsDelayedInstantiation;
	}
}

这里可能有个疑问:

staticArguments服务初始化的静态参数我理解,就是一些初始化时候需要的参数,但是如果初始化的时候需要的是一个服务呢,该怎么办?

这个大家其实不用担心,后面实例化的时候会讲解到,大家可以先只知道是初始化需要的参数即可,

总结

通过上述结构,我们其实知道:SyncDescriptor就是描述了一个类实例化的时候需要的信息的一个东西,在后续真正实例化的时候,需要通过它去实例化对象。

服务集合-ServiceCollection

了解了上述概念,我们再来看服务集合。

import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor } from './descriptors';

export class ServiceCollection {

	private _entries = new Map<ServiceIdentifier<any>, any>(); // 存储服务的 ma p

	constructor(...entries: [ServiceIdentifier<any>, any][]) { // 初始化的时候可以传入一些服务
		for (let [id, service] of entries) {
			this.set(id, service);
		}
	}

 // 添加一个 服务实例 或者 服务装饰器
	set<T>(id: ServiceIdentifier<T>, instanceOrDescriptor: T | SyncDescriptor<T>): T | SyncDescriptor<T> {
		const result = this._entries.get(id);
		this._entries.set(id, instanceOrDescriptor);
		return result;
	}

  // 检测是否已经存在
	has(id: ServiceIdentifier<any>): boolean {
		return this._entries.has(id);
	}

  // 获得一个服务实例或者服务装饰器
	get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
		return this._entries.get(id);
	}
}

看起来,现在看的明明白白了。

Startup

那我们继续startup函数

private async startup(args: NativeParsedArgs): Promise<void> {
		const bufferLogService = new BufferLogService();
		const [instantiationService, instanceEnvironment, environmentService] = this.createServices(
      args,      
      bufferLogService);
}

这里是创建了一个bufferLogService,这个就不展开了,感兴趣可以自己看这个服务干啥的,不影响主流程,主要是:createServices, 这里面注册了很多服务,我只列举关键的来讲解这个流程,其余先省略。

	private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] {
		const services = new ServiceCollection();
    ...
		const environmentService = new EnvironmentMainService(args);
		services.set(IEnvironmentMainService, environmentService);// 服务实例本身
    ... 
		services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); // 装饰器
    ...
		services.set(IProductService, { _serviceBrand: undefined, ...product }); // 服务实例本身第二种写法

		return [new InstantiationService(services, true), instanceEnvironment, environmentService];
	}

可以看到,创建了服务集合ServiceCollection并将其全部放入:new InstantiationService(services, true), 这里在服务集合里也放了两种类型,一个是:服务实例本身;一个是装饰器。其中有一个IProductService比较偷懒,没写类,直接构造了个对象传递进去。

其实到这里,最重要的就是new InstantiationService(services, true), 这个主要去管理和实例化服务的地方,也是 IOC 机制的关键类。

今天先到这里了,上班咯!