亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

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

myeveryheart / 4267人閱讀

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

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

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

需求描述

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

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

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

Angular 官網(wǎng)也給出了范例,以下代碼可以實(shí)現(xiàn)一個(gè) 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);
  }
}
問(wèn)題描述

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

依賴(lài)關(guān)系1

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

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

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

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

從下述源碼可以看出,HttpClient 服務(wù)依賴(lài)注入了 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 的依賴(lài)中包含可選的 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 是一個(gè) InjectionToken 實(shí)例,用于標(biāo)識(shí)所有攔截器服務(wù)。new Inject(HTTP_INTERCEPTORS) 可以獲取攔截器服務(wù)的實(shí)例們。

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

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

也就是說(shuō),HttpClient 依賴(lài)于所有 HttpInterceptors,包括 AuthInterceptor

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

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

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

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

// 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)部通過(guò) Map 類(lèi)型的 _seenProviders 來(lái)記錄看到過(guò)的供應(yīng)商。

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

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

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

圖二、截圖時(shí) token 變量的值:

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

用戶(hù)的修復(fù)

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

我們可以通過(guò)注入 Injector 手動(dòng)懶加載 AuthService 而不是直接注入其到 constructor,來(lái)使依賴(lài)關(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);
  }
}

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

由此我們繞開(kāi)了編譯階段的對(duì)循環(huán)依賴(lài)做的檢查。

官方的修復(fù)

就像 PR 里提到的這樣:

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

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

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

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

// 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 依賴(lài)的創(chuàng)建方式由原來(lái)的使用 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 類(lèi)(代碼三):

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

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

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

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

因?yàn)?AuthInterceptor 對(duì) AuthService 的引用和 AuthService 對(duì) HttpClient 的引用是用戶(hù)定義的,所以官方可以控制的只剩下 HttpClient 到攔截器的依賴(lài)引用了。所以,官方選擇從 HttpClient 處切斷依賴(lài)。

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

我覺(jué)得原因有二:

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

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

后記

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

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

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

Angular CHANGELOG.md

fix(common): allow HttpInterceptors to inject HttpClient

Insider’s guide into interceptors and HttpClient mechanics in Angular:這篇寫(xiě)得相當(dāng)?shù)煤?,深入了攔截器和 HttpClient 的內(nèi)部機(jī)制,推薦閱讀!

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97060.html

相關(guān)文章

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

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

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

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

    MiracleWong 評(píng)論0 收藏0
  • 我理解的 core 目錄

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

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

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

    e10101 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<