1. 程式人生 > >Angular2 依賴注入之例項化過程

Angular2 依賴注入之例項化過程

這裡會介紹 Angular 的注入器和例項化過程,基於 [email protected]

Angular 依賴注入過程主要有下面幾個重要的部分組成:

  • Inject 和 Injectable 裝飾器
  • 解析提供商,構造注入器
  • 獲取例項化物件

Angular 依賴注入中的一些重要的概念:

Provider :
提供商,下面就是一個Proviver,一共有5種構造提供商的方式:TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider

{ provide: Logger, useClass: Logger }

Token :
令牌,提供商中的第一個引數就是Token,在查詢依賴時作為 key 使用。

Injector :
注入器,用於解析依賴和建立物件。

Example

class Engine {
  start() {
    console.log('engine start');
  }
}

class Car {
  engine: Engine;
  constructor(@Inject(Engine) engine) {
    this.engine = engine;
  }
  open() {
    this.engine.start();
    console.log('car open'
); } } let inj = ReflectiveInjector.resolveAndCreate([ Car, Engine ]); let car = inj.get(Car); car.open();

2. 解析服務提供商,構造注入器

在上面註解完成之後,接下來可以構造 Injector,Injector 首先需要解析 Provider.

let inj = ReflectiveInjector.resolveAndCreate([
  {provide:Car,useClass:Car},
  {provide:Engine,useClass:Engine}
]);

Provider 的解析主要由 resolveReflectiveProviders() 來完成,解析過程主要分為

  • provider 引數預處理
  • 解析例項化工廠及其依賴
  • 合併提供商
  • 構造注入器

下面是解析過程的原始碼

export function resolveReflectiveProviders(providers: Provider[]): ResolvedReflectiveProvider[] {
  const normalized = _normalizeProviders(providers, []);
  const resolved = normalized.map(resolveReflectiveProvider);
  const resolvedProviderMap = mergeResolvedReflectiveProviders(resolved, new Map());
  return Array.from(resolvedProviderMap.values());
}

2.1 引數預處理

_normalizeProviders 是將 provider 引數標準化,將省略了 useClass 的補全,將巢狀的 Provider 陣列拍平

function _normalizeProviders(providers: Provider[], res: Provider[]): Provider[] {
  providers.forEach(b => {

    //如果 provider 是建構函式,對應沒有提供useClass的情況
    if (b instanceof Type) {
      res.push({provide: b, useClass: b});

    //如果是物件,且帶provide屬性,直接放入陣列
    } else if (b && typeof b == 'object' && (b as any).provide !== undefined) {
      res.push(b as NormalizedProvider);

    //如果是陣列,遞迴拍平
    } else if (b instanceof Array) {
      _normalizeProviders(b, res);

    } else {
      throw invalidProviderError(b);
    }
  });

  return res;
}

2.2 解析例項化工廠及其依賴

resolveReflectiveProvider 函式會根據 ReflectiveKey 和 ResolvedReflectiveFactory 返回一個 ResolvedReflectiveProvider_ 物件。

function resolveReflectiveProvider(provider: NormalizedProvider): ResolvedReflectiveProvider {
  return new ResolvedReflectiveProvider_(
      ReflectiveKey.get(provider.provide), [resolveReflectiveFactory(provider)],
      provider.multi || false);
}

其中的 resolveReflectiveFactory() 函式是其中的重點,解析的例項化工程和以來的方式有4鍾,分別對應 4種 Provider 的建立方式,分別是

  • useClass
  • useExisting
  • useFactory
  • useValue

這個函式裡面會根據這四種方式提供不同的例項化工廠和依賴解析,依賴解析完成會返回一個 ReflectiveDependency 陣列。

ReflectiveDependency 的建構函式為

constructor(
      public key: ReflectiveKey, public optional: boolean, 
      public visibility: Self|SkipSelf|null)

ReflectiveKey 有兩個屬性: id 和 token ,其內部維持了一個Map

2.2.1 useClass

例項化工廠

useClass 的情況下例項化就通過 new 的方式完成

factoryFn = (...args: any[]) => new t(...args)

依賴解析方式

從 Inject 和 Injectable 兩個裝飾器中儲存的資訊尋找依賴的建構函式,也就是從 ‘parameters’ 和 ‘design:paramtypes’ 兩個元資料中尋找。如果當前建構函式沒有,回從原型鏈向上查詢

2.2.2 useExisting

例項化工廠

useExisting 情況下是一個返回傳入引數的函式,也就是返回依賴的例項

factoryFn = (aliasInstance: any) => aliasInstance;

依賴解析方式

將 useExisting 的值作為其依賴,也就是作為後面例項化時的引數

[ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting))]

2.2.3 useFactory

例項化工廠

useFactory 的情況下就是useFactory的值

factoryFn = provider.useFactory;

依賴解析方式

如果定義了 deps,則直接從 deps 中抽取出 token 並構造ReflectiveDependency

否則使用和 useClass 相同的方法解析依賴

2.2.4 useValue

例項化工廠

直接返回這個值

factoryFn = () => provider.useValue;

依賴解析方式

空陣列

2.3 合併提供商

Angular 允許為一個 token 提供多個 provider, 只要申明 multi:true 即可。

let inj = ReflectiveInjector.resolveAndCreate([
{provide:Car,useClass:Car,multi:true},
{provide:Car,useClass:SuperCar,multi:true},
{provide:Engine,useClass:Engine}
]);
let car = inj.get(Car); //car = [Car, SuperCar]

此時使用 injector.get() 方法返回的是一個數組。 Angular 使用這種機制來提供可插拔的介面(pluggable hooks).

mergeResolvedReflectiveProviders() 函式就是用來處理多提供商的情況的,對申明 multi:true 的提供商,它會為一個 token 提供多個值。

2.4 構造注入器

ReflectiveInjector 的構造器如下,使用上面解析完成的 ResolvedReflectiveProvider_ 和父注入器例項化。

constructor(_providers: ResolvedReflectiveProvider[], _parent?: Injector)

3. 從注入器獲取物件

從注入器中獲取物件主要分為以下幾步:

  • 判斷從哪個注入器獲取物件
  • 從快取中查詢
  • 例項化物件及其依賴

3.1 判斷 Self 和 SkipSelf

預設是如果當前注入器沒找到,則遞歸向父注入器查詢

self 只從當前注入器查詢
SkipSelf 從父注入器開始查詢

while (inj instanceof ReflectiveInjector_) {
  const inj_ = <ReflectiveInjector_>inj;
  const obj = inj_._getObjByKeyId(key.id);
  if (obj !== UNDEFINED) return obj;
  inj = inj_._parent;
}

3.2 快取

ReflectiveInjector_ 物件中有兩名兩個屬性,主要用於快取物件。 keyIds 是所有 ReflectiveKey 的 id, objs 是快取的已經例項化的物件。

keyIds: number[];
objs: any[];

根據 ReflectiveKey 的 Id 判斷是否已經例項化。 如果沒有就 new 一個。

private _getObjByKeyId(keyId: number): any {
  for (let i = 0; i < this.keyIds.length; i++) {
    if (this.keyIds[i] === keyId) {
      if (this.objs[i] === UNDEFINED) {
        //例項化物件,並快取到objs
        this.objs[i] = this._new(this._providers[i]);
      }
      return this.objs[i];
    }
  }

  return UNDEFINED;
}

3.3 例項化

例項化物件會首先例項化其需要的依賴,然後再根據由其例項化工廠來處理。

private _instantiate(
    provider: ResolvedReflectiveProvider,
    ResolvedReflectiveFactory: ResolvedReflectiveFactory): any {
  const factory = ResolvedReflectiveFactory.factory;

  let deps: any[];
  try {
    //例項化所有依賴
    deps =
        ResolvedReflectiveFactory.dependencies.map(dep => this._getByReflectiveDependen(dep));
  } catch (e) {
    if (e.addKey) {
      e.addKey(this, provider.key);
    }
    throw e;
  }

  let obj: any;
  try {
    //呼叫例項化工程,並將依賴作為引數傳遞進去
    obj = factory(...deps);
  } catch (e) {
    throw instantiationError(this, e, e.stack, provider.key);
  }

  return obj;
}

相關推薦

Angular2 依賴注入例項過程

這裡會介紹 Angular 的注入器和例項化過程,基於 [email protected] Angular 依賴注入過程主要有下面幾個重要的部分組成: Inject 和 Injectable 裝飾器 解析提供商,構造注入器 獲取例項化物件

[轉]Spring依賴注入例項(new)的差別

Spring依賴注入和例項化(new)的差別。 Sping為什麼使用依賴注入而不使用例項化物件的方式? 首先說明一下概念 依賴注入(Dependency of Injection)和控制反轉(Inversion of Control 簡稱

Angular2 依賴注入裝飾器

下面會從 Angular 的原始碼分析依賴注入中的的裝飾器,基於 [email protected] Angular 依賴注入過程主要有下面幾個重要的部分組成: Inject 和 Injectable 裝飾器 解析提供商,構造注入器 獲取例項化物

依賴注入Bean的例項

上一篇分析到了通過遞迴的方式獲取Bean的所有依賴,接下來就該例項化Bean了。 對於例項化Bean,Spring提供了兩種方式,一種是Jdk的反射功能,還有一種就是Cglib。這兩種例項化的方式的區別是什麼呢? 依賴注入的起點是getBean方法,然後

springboot bean的例項過程和屬性注入過程

瞭解過springboot的載入流程的都知道springboot初始化bean都在refresh方法中。這個方法程式碼如下:// Prepare this context for refreshing. prepareRefresh(); // Tell the subcla

java面向物件:子類物件的例項過程詳解

在子類建構函式中,發現,訪問子類建構函式時,父類也運行了。原因是什麼呢? 在子類的建構函式裡第一行有一個預設的隱式語句:super() ExtendsDemo.java class Fu { Fu() { System.out.println("fu run")

spring源碼分析初始過程

源碼分析 true singleton 存在 factory 源碼 org 包含 eric 1.org.springframework.web.context.ContextLoaderListener 一個ServletContextListener,web容器啟動監聽器

面向物件【day07】:類的例項過程剖析(三)

本節內容 1、概述 2、類的語法 3、總結 一、概述    之前我們說關於python中的類,都一臉懵逼,都想說,類這麼牛逼到底是什麼,什麼才是類?下面我們就來講講,什麼是類?它具有哪些特性。 二、類的語法 2.1 語法

JVM總括四-類載入過程、雙親委派模型、物件例項過程 JVM思考-init和clinit區別

JVM總括四-類載入過程、雙親委派模型、物件例項化過程 目錄:JVM總括:目錄 一、 類載入過程 類載入過程就是將.class檔案轉化為Class物件,類例項化的過程,(User user = new User(); 這個過程是物件例項化的過程); 一個.class檔案只有一個Class物件(位元

JVM思考-init和clinit區別 JVM總括四-類載入過程、雙親委派模型、物件例項過程

JVM思考-init和clinit區別 目錄:JVM總括:目錄 clinit和init的區別其實也就是Class物件初始化物件初始化的區別,詳情看我上一篇部落格:  JVM總括四-類載入過程、雙親委派模型、物件例項化過程 一、init和clinit方法執行時機不同   init是物件構

JVM總括四-類載入過程、雙親委派模型、物件例項過程

JVM總括四-類載入過程、雙親委派模型、物件例項化過程 目錄:JVM總括:目錄 一、 類載入過程 類載入過程就是將.class檔案轉化為Class物件, 類例項化 的過程 ,(User user = new User(); 這個過程是 物件例項化 的

sql注入與防止SQL注入引數處理

sql注入的兩種情況: 操作程式碼: import pymysql user = input('使用者名稱: ').strip() pwd = input('密碼: ').strip() # 連結 conn = pymysql.connect(host='localhost', user='ro

啟動期間的記憶體管理初始過程概述----Linux記憶體管理(九)

在記憶體管理的上下文中, 初始化(initialization)可以有多種含義. 在許多CPU上, 必須顯式設定適用於Linux核心的記憶體模型. 例如在x86_32上需要切換到保護模式, 然後核心才能檢測到可用記憶體和暫存器. 而我們今天要講的boot階段

類載入過程(clinit()),物件例項過程(init())

類載入過程: 1、類載入就是執行Java程式編譯之後在位元組碼檔案中生成的clinit()方法(稱之為類構造器),clinit()方法由靜態變數和靜態程式碼塊組成。 2、子類的載入首先需要先載入父類,如果父類為介面。則不會呼叫父類的clinit方法。一個類中可以沒有clinit方法。 3、clinit方

spring原始碼閱讀(1)- ioc依賴注入bean載入

還是先看下DefaultListableBeanFactory的類結構圖  我們從User user = (User) beanFactory.getBean("user");入手進入bean的載入管理流程。 這裡還是堅持走主線的流程,去掉無關的枝葉,儘量讓業務變得簡

子類物件例項過程

2018年11月05日 08:58:03 aprildusk 閱讀數:3 個人分類: java

tomcat原始碼解讀初始過程

之前我拜讀了《How Tomcat Works》這本書,對tomcat的架構與裡面的實現有了一定的瞭解,現在藉著tomcat的原始碼來深入瞭解這個精巧的藝術品。 首先從初始化開始, getServer().init(); 前面類載入的過程與digester讀

Java子父類中的建構函式例項過程

其實我們發現子類繼承父類操作很簡單,如果要是去深入的研究下會發現,例項化過程並非是我們看到的那樣,我們就以程式碼舉例來說明; 問大家,以下程式碼執行會輸出什麼呢? package com.oop; /** * 定義動物物件 * @author

spring裝配Bean過程以及Bean例項過程

<ol class="linenums" style="margin:0px; word-wrap:break-word; padding:0px 0px 0px 40px"><li class="L0" style="word-wrap:break-word; padding-left:

Java依賴注入(DI)例項詳解

Java依賴注入模式允許我們擺脫硬編碼,使我們的應用更加鬆耦合、增強擴充套件性以及可維護性。通過依賴注入我們可以降低從編譯到執行時的依賴性。 Java依賴注入 Java的依賴注入僅僅通過理論是很難解明白的,所以我們通過幾個簡單的示例來描述它,怎樣利用依賴