国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[NG] 考古 - HttpInterceptor 循環(huán)引用錯誤

myeveryheart / 4197人閱讀

摘要:官網(wǎng)也給出了范例,以下代碼可以實(shí)現(xiàn)一個攔截器問題描述但在之前,執(zhí)行上述官方給出的代碼是會報(bào)錯的。可以獲取攔截器服務(wù)的實(shí)例們。

原文首發(fā)于 baishusama.github.io,歡迎圍觀~
前言

恍然間發(fā)現(xiàn)這個錯誤已經(jīng)不復(fù)存在了,于是稍微看了下相關(guān) issue、commit、PR。寫篇筆記祭奠下~

需求描述

一個使用 HttpInterceptor 的常見場景是實(shí)現(xiàn)基于 token 的驗(yàn)證機(jī)制。

為什么要使用攔截(intercepting)呢?

因?yàn)椋诨?token 的驗(yàn)證機(jī)制中,證明用戶身份的 token 需要被附帶在每一個(需要驗(yàn)證的請求的)請求頭。如果不使用攔截手段,那么(由 HttpClient 實(shí)例觸發(fā)的)每一個請求都需要手動修改請求頭(header)。顯然手動修改是繁瑣和難以維護(hù)的。所以,我們選擇做攔截。

Angular 官網(wǎng)也給出了范例,以下代碼可以實(shí)現(xiàn)一個 AuthInterceptor 攔截器:

import { Injectable } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import { AuthService } from "../auth.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest, next: HttpHandler): Observable> {
    const authToken = this.auth.getAuthorizationToken();

    const authReq = req.clone({
      headers: req.headers.set("Authorization", authToken)
    });

    return next.handle(authReq);
  }
}
問題描述

但在 5.2.3 之前,執(zhí)行上述官方給出的代碼是會報(bào)錯的。原因是 存在循環(huán)引用問題

依賴關(guān)系1

我們看一下上述代碼:AuthInterceptor 由于需要使用 AuthService 服務(wù)提供的獲取 token 的方法,依賴注入了 AuthService

AuthInterceptor -> AuthService  // AuthInterceptor 攔截器需要 AuthService 服務(wù)來獲取 token
依賴關(guān)系2

而一般情況下我們的 AuthService 需要做登錄登出等操作,特別是需要和后端交互以獲取 token,所以需要依賴注入 HttpClient,存在依賴關(guān)系:

AuthService -> HttpClient // AuthService 服務(wù)需要 HttpClient 服務(wù)來和后端交互
依賴關(guān)系3

從下述源碼可以看出,HttpClient 服務(wù)依賴注入了 HttpHandler

// v5.2.x
export class HttpClient {
  constructor(private handler: HttpHandler) {}

  request(...): Observable {
    let req: HttpRequest;
    ...
    // Start with an Observable.of() the initial request, and run the handler (which
    // includes all interceptors) inside a concatMap(). This way, the handler runs
    // inside an Observable chain, which causes interceptors to be re-run on every
    // subscription (this also makes retries re-run the handler, including interceptors).
    const events$: Observable> =
        concatMap.call(of (req), (req: HttpRequest) => this.handler.handle(req));
    ...
}

HttpHandler 的依賴中包含可選的 new Inject(HTTP_INTERCEPTORS)

// v5.2.2
@NgModule({
  imports: [...],
  providers: [
    HttpClient,
    // HttpHandler is the backend + interceptors and is constructed
    // using the interceptingHandler factory function.
    {
      provide: HttpHandler,
      useFactory: interceptingHandler,
      deps: [HttpBackend, [new Optional(), new Inject(HTTP_INTERCEPTORS)]],
    },
    HttpXhrBackend,
    {provide: HttpBackend, useExisting: HttpXhrBackend},
    ...
  ],
})
export class HttpClientModule {
}

其中,HTTP_INTERCEPTORS 是一個 InjectionToken 實(shí)例,用于標(biāo)識所有攔截器服務(wù)。new Inject(HTTP_INTERCEPTORS) 可以獲取攔截器服務(wù)的實(shí)例們。

這里的“token”是 Angular 的 DI 系統(tǒng)中用于標(biāo)識以來對象的東西。token 可以是字符串或者 Type/InjectionToken/OpaqueToken 類的實(shí)例。

P.S. 關(guān)于使用哪一種 token 更好的問題,可以【TODO:】看一下這篇文章(譯文)。

也就是說,HttpClient 依賴于所有 HttpInterceptors,包括 AuthInterceptor

HttpClient -> AuthInterceptor // HttpClient 服務(wù)需要 AuthInterceptor 在內(nèi)的所有攔截器服務(wù)來處理請求
循環(huán)依賴

綜上,我們有循環(huán)依賴:

AuthInterceptor -> AuthService -> HttpClient -> AuthInterceptor -> ...

而在 Angular 里,每一個服務(wù)實(shí)例的初始化所需要的依賴都是需要事先準(zhǔn)備好的,但一個循環(huán)依賴是永遠(yuǎn)也準(zhǔn)備不好的……Angular 因此會檢測循環(huán)依賴的存在,并在循環(huán)依賴被檢測到時報(bào)錯,部分源碼如下:

// v5.2.x
export class NgModuleProviderAnalyzer {
  private _transformedProviders = new Map();
  private _seenProviders = new Map();
  private _allProviders: Map;
  private _errors: ProviderError[] = [];

  ...

  private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst|null {
    const resolvedProvider = this._allProviders.get(tokenReference(token));
    if (!resolvedProvider) {
      return null;
    }
    let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
    if (transformedProviderAst) {
      return transformedProviderAst;
    }
    if (this._seenProviders.get(tokenReference(token)) != null) {
      this._errors.push(
        new ProviderError(`Cannot instantiate cyclic dependency! ${tokenName(token)}`, resolvedProvider.sourceSpan));
      return null;
    }
    this._seenProviders.set(tokenReference(token), true);
    ...
  }
}

讓我們稍微看一下代碼:

NgModuleProviderAnalyzer 內(nèi)部通過 Map 類型的 _seenProviders 來記錄看到過的供應(yīng)商。

在其方法 _getOrCreateLocalProvider 內(nèi)部判斷是否已經(jīng)看過,如果已經(jīng)看過會在 _errors 中記錄一個 ProviderError 錯誤。

我用 5.2.2 版本的 Angular 編寫了一個遵循官方文檔寫法但出現(xiàn)“循環(huán)引用錯誤”的示例項(xiàng)目。下面是我 ng serve 運(yùn)行該應(yīng)用后,在 compiler.js 中添加斷點(diǎn)調(diào)試得到的結(jié)果:

圖一、截圖時 _seenProviders 中已經(jīng)記錄的各個供應(yīng)商:

圖二、截圖時 token 變量的值:

在上述截圖中,根據(jù)圖二的 token 變量是能在 _seenProviders 中獲取到非 null 值的,所以會向 _errors 中記錄一個 Cannot instantiate cyclic dependency! 開頭的錯誤。當(dāng)執(zhí)行完所有代碼之后,控制臺會出現(xiàn)該錯誤:

用戶的修復(fù)

那么在 5.2.2 及以前,作為 Angular 開發(fā)者,要如何解決上述問題呢?

我們可以通過注入 Injector 手動懶加載 AuthService 而不是直接注入其到 constructor,來使依賴關(guān)系變?yōu)槿缦拢?/p>

AuthInterceptor --x-> AuthService -> HttpClient -> AuthInterceptor --x->
即 AuthService -> HttpClient -> AuthInterceptor,其中,在 AuthInterceptor 中懶加載 AuthService

即將官方的示例代碼修改為如下:

import { Injectable, Injector } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs/Observable";
import { AuthService } from "../auth.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private auth: AuthService;

  constructor(private injector: Injector) {}

  intercept(req: HttpRequest, next: HttpHandler): Observable> {
    this.auth = this.injector.get(AuthService);

    const authToken = this.auth.getAuthorizationToken();

    const authReq = req.clone({
      headers: req.headers.set("Authorization", authToken)
    });

    return next.handle(authReq);
  }
}

可以看到和官方的代碼相比,我們改為依賴注入 Injector,并通過其實(shí)例對象 this.injector 在調(diào)用 intercept 方法時才去獲取 auth 服務(wù)實(shí)例,而不是將 auth 作為依賴注入、在調(diào)用構(gòu)造函數(shù)的時候去獲取。

由此我們繞開了編譯階段的對循環(huán)依賴做的檢查。

官方的修復(fù)

就像 PR 里提到的這樣:

Either HttpClient or the user has to deal specially with the circular dependency.

所以,為了造福大眾,最終官方做出了修改,原理和作為用戶的我們的代碼的思路是一致的——利用懶加載解決循環(huán)依賴問題!

因?yàn)樾迯?fù)的代碼量很少,所以這里整個摘錄下。

首先,新增 HttpInterceptingHandler 類(代碼一):

// v5.2.3
/**
 * An `HttpHandler` that applies a bunch of `HttpInterceptor`s
 * to a request before passing it to the given `HttpBackend`.
 *
 * The interceptors are loaded lazily from the injector, to allow
 * interceptors to themselves inject classes depending indirectly
 * on `HttpInterceptingHandler` itself.
 */
@Injectable()
export class HttpInterceptingHandler implements HttpHandler {
  private chain: HttpHandler|null = null;

  constructor(private backend: HttpBackend, private injector: Injector) {}

  handle(req: HttpRequest): Observable> {
    if (this.chain === null) {
      const interceptors = this.injector.get(HTTP_INTERCEPTORS, []);
      this.chain = interceptors.reduceRight(
          (next, interceptor) => new HttpInterceptorHandler(next, interceptor), this.backend);
    }
    return this.chain.handle(req);
  }
}

HttpHandler 依賴的創(chuàng)建方式由原來的使用 useFactory: interceptingHandler 函數(shù)(代碼二):

// v5.2.2
@NgModule({
  imports: [...],
  providers: [
    HttpClient,
    // HttpHandler is the backend + interceptors and is constructed
    // using the interceptingHandler factory function.
    {
      provide: HttpHandler,
      useFactory: interceptingHandler,
      deps: [HttpBackend, [new Optional(), new Inject(HTTP_INTERCEPTORS)]],
    },
    HttpXhrBackend,
    {provide: HttpBackend, useExisting: HttpXhrBackend},
    ...
  ],
})
export class HttpClientModule {
}

改為使用 useClass: HttpInterceptingHandler 類(代碼三):

// v5.2.3
@NgModule({
  imports: [...],
  providers: [
    HttpClient,
    {provide: HttpHandler, useClass: HttpInterceptingHandler},
    HttpXhrBackend,
    {provide: HttpBackend, useExisting: HttpXhrBackend},
    ...
  ],
})
export class HttpClientModule {
}

不難發(fā)現(xiàn),在“代碼一”中我們看到了熟悉的寫法:依賴注入 Injector,并通過其實(shí)例對象 this.injector 在調(diào)用 handle 方法時才去獲取 HTTP_INTERCEPTORS 攔截器依賴,而不是將 interceptors 作為依賴注入(在調(diào)用構(gòu)造函數(shù)的時候去獲取)。

也就是官方修復(fù)的思路如下:

AuthInterceptor -> AuthService -> HttpClient -x-> AuthInterceptor
即 AuthInterceptor -> AuthService -> HttpClient,其中,在 HttpClient 中懶加載 interceptors

因?yàn)?AuthInterceptorAuthService 的引用和 AuthServiceHttpClient 的引用是用戶定義的,所以官方可以控制的只剩下 HttpClient 到攔截器的依賴引用了。所以,官方選擇從 HttpClient 處切斷依賴。

那么,我們?yōu)槭裁催x擇從 AuthInterceptor 處而不是從 AuthService 處切斷依賴呢?

我覺得原因有二:

一個是為了讓 AuthService 盡可能保持透明——對 interceptor 引起的問題沒有察覺。因?yàn)楸举|(zhì)上這是 interceptors 不能依賴注入 HttpClient 的問題。

另一個是 AuthService 往往有很多能觸發(fā) HttpClient 使用的方法,那么在什么時候去通過 injector 來 get HttpClient 服務(wù)實(shí)例呢?或者說所有方法都加上相關(guān)判斷么?……所以為了避免問題的復(fù)雜化,選擇選項(xiàng)更少(只有一個 intercept 方法)的 AuthInterceptor 顯然更為明智。

后記

還是太年輕,以前翻 github 的時候沒有及時訂閱 issue,導(dǎo)致一些問題修復(fù)了都毫無察覺……

從今天起,好好訂閱 issue,好好整理筆記,共勉~

P.S. 好久沒寫文章了,這篇文章簡直在劃水……所以我肯定很多地方?jīng)]講清楚(特別是代碼都沒有細(xì)講),各位看官哪里沒看明白的請務(wù)必指出,我會根據(jù)需要慢慢補(bǔ)充。望輕拍磚(逃
參考

Angular CHANGELOG.md

fix(common): allow HttpInterceptors to inject HttpClient

Insider’s guide into interceptors and HttpClient mechanics in Angular:這篇寫得相當(dāng)?shù)煤茫钊肓藬r截器和 HttpClient 的內(nèi)部機(jī)制,推薦閱讀!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97060.html

相關(guān)文章

  • HttpInterceptor 攔截器 - 網(wǎng)絡(luò)請求超時與重試的簡單實(shí)現(xiàn)

    摘要:對象表示攔截器鏈表中的下一個攔截器。至此,攔截器只會再重試到最大次數(shù)還是失敗的情況下拋出超時錯誤。完成上述步驟,一個簡單的網(wǎng)絡(luò)請求超時與重試的攔截器便實(shí)現(xiàn)了。 ... 攔截器在Angular項(xiàng)目中其實(shí)有著十分重要的地位,攔截器可以統(tǒng)一對 HTTP 請求進(jìn)行攔截處理,我們可以在每個請求體或者響應(yīng)后對應(yīng)的流添加一系列動作或者處理數(shù)據(jù),再返回給使用者調(diào)用。 每個 API 調(diào)用的時候都不可避免...

    stonezhu 評論0 收藏0
  • 使用ng2-admin搭建成熟可靠的后臺系統(tǒng) -- ng2-admin(五)

    摘要:創(chuàng)建一個工具類,負(fù)責(zé)提供以及完成拼接參數(shù)的工作。根據(jù)我們的配置,來創(chuàng)建這個文件。因?yàn)槭潜韱翁峤唬晕覀冃陆ㄒ粋€服務(wù),由它來完成表單提交的最后一步。 使用ng2-admin搭建成熟可靠的后臺系統(tǒng) -- ng2-admin(五) 完善動態(tài)表單組件 升級Angular 4.1 -> 4.3 添加 json-server 模擬數(shù)據(jù) 創(chuàng)建自己的 http 完成一次表單提交 升級Angu...

    MiracleWong 評論0 收藏0
  • 我理解的 core 目錄

    摘要:只需引入一次的什么是項(xiàng)目中只需要引入一次的舉個例子,全局錯誤處理根路由數(shù)據(jù)預(yù)加載請求攔截器等。更漂亮的是為我們提供了攔截器接口,我們只管開發(fā)攔截器邏輯功能,調(diào)用及使用全部控制權(quán)都在框架內(nèi)。 ... 過了一遍 Angular 文檔 的小伙伴大致都會記得最佳實(shí)踐中提到過的有關(guān)CoreModule的一些解釋和說明,其實(shí)關(guān)于名字的命名不是強(qiáng)制性的,只要團(tuán)隊(duì)中一致 pass,你把它命名為XXXM...

    callmewhy 評論0 收藏0
  • 簽發(fā)的用戶認(rèn)證token超時刷新策略

    摘要:簽發(fā)的用戶認(rèn)證超時刷新策略這個模塊分離至項(xiàng)目權(quán)限管理系統(tǒng)與前后端分離實(shí)踐,感覺那樣太長了找不到重點(diǎn),分離出來要好點(diǎn)。這樣在有效期過后的時間段內(nèi)可以申請刷新。 簽發(fā)的用戶認(rèn)證token超時刷新策略 這個模塊分離至項(xiàng)目api權(quán)限管理系統(tǒng)與前后端分離實(shí)踐,感覺那樣太長了找不到重點(diǎn),分離出來要好點(diǎn)。 對于登錄的用戶簽發(fā)其對應(yīng)的jwt,我們在jwt設(shè)置他的固定有效期時間,在有效期內(nèi)用戶攜帶jw...

    e10101 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<