欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Feign的實(shí)現(xiàn)原理

 更新時(shí)間:2021年06月29日 14:47:59   作者:mghio  
Feign是Netflix開(kāi)發(fā)的聲明式、模板化的HTTP客戶(hù)端, Feign可以幫助我們更快捷、優(yōu)雅地調(diào)用HTTP API

一、什么是Feign

Feign 是⼀個(gè) HTTP 請(qǐng)求的輕量級(jí)客戶(hù)端框架。通過(guò) 接口 + 注解的方式發(fā)起 HTTP 請(qǐng)求調(diào)用,面向接口編程,而不是像 Java 中通過(guò)封裝 HTTP 請(qǐng)求報(bào)文的方式直接調(diào)用。服務(wù)消費(fèi)方拿到服務(wù)提供方的接⼝,然后像調(diào)⽤本地接⼝⽅法⼀樣去調(diào)⽤,實(shí)際發(fā)出的是遠(yuǎn)程的請(qǐng)求。讓我們更加便捷和優(yōu)雅的去調(diào)⽤基于 HTTP 的 API,被⼴泛應(yīng)⽤在 Spring Cloud 的解決⽅案中。開(kāi)源項(xiàng)目地址:Feign,官方描述如下:

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.

二、為什么用Feign

Feign 的首要目標(biāo)就是減少 HTTP 調(diào)用的復(fù)雜性。在微服務(wù)調(diào)用的場(chǎng)景中,我們調(diào)用很多時(shí)候都是基于 HTTP 協(xié)議的服務(wù),如果服務(wù)調(diào)用只使用提供 HTTP 調(diào)用服務(wù)的 HTTP Client 框架(e.g. Apache HttpComponnets、HttpURLConnection OkHttp 等),我們需要關(guān)注哪些問(wèn)題呢?

相比這些 HTTP 請(qǐng)求框架,F(xiàn)eign 封裝了 HTTP 請(qǐng)求調(diào)用的流程,而且會(huì)強(qiáng)制使用者去養(yǎng)成面向接口編程的習(xí)慣(因?yàn)?Feign 本身就是要面向接口)。

三、實(shí)例

3.1、原生使用方式

以獲取 Feign 的 GitHub 開(kāi)源項(xiàng)目的 Contributors 為例,原生方式使用 Feign 步驟有如下三步(這里以使用 Gradle 進(jìn)行依賴(lài)管理的項(xiàng)目為例):

第一步: 引入相關(guān)依賴(lài):implementation 'io.github.openfeign:feign-core:11.0'

在項(xiàng)目的 build.gradle 文件的依賴(lài)聲明處 dependencies 添加該依賴(lài)聲明即可。

第二步: 聲明 HTTP 請(qǐng)求接口

使用 Java 的接口和 Feign 的原生注解 @RequestLine 聲明 HTTP 請(qǐng)求接口,從這里就可以看到 Feign 給使用者封裝了 HTTP 的調(diào)用細(xì)節(jié),極大的減少了 HTTP 調(diào)用的復(fù)雜性,只要定義接口即可。

第三步: 配置初始化 Feign 客戶(hù)端

最后一步配置初始化客戶(hù)端,這一步主要是設(shè)置請(qǐng)求地址、編碼(Encoder)、解碼(Decoder)等。

通過(guò)定義接口,使用注解的方式描述接口的信息,就可以發(fā)起接口調(diào)用。最后請(qǐng)求結(jié)果如下:

3.2、結(jié)合 Spring Cloud 使用方式

同樣還是以獲取 Feign 的 GitHub 開(kāi)源項(xiàng)目的 Contributors 為例,結(jié)合 Spring Cloud 的使用方式有如下三步:

第一步: 引入相關(guān) starter 依賴(lài):org.springframework.cloud:spring-cloud-starter-openfeign

在項(xiàng)目的 build.gradle 文件的依賴(lài)聲明處 dependencies 添加該依賴(lài)聲明即可。

第二步: 在項(xiàng)目的啟動(dòng)類(lèi) XXXApplication 上添加 @EnableFeignClients 注解啟用 Feign 客戶(hù)端功能。

第三步: 創(chuàng)建 HTTP 調(diào)用接口,并添加聲明 @FeignClient 注解。

最后一步配置初始化客戶(hù)端,這一步主要是設(shè)置請(qǐng)求地址(url)、編碼(Encoder)、解碼(Decoder)等,與原生使用方式不同的是,現(xiàn)在我們是通過(guò) @FeignClient 注解配置的 Feign 客戶(hù)端屬性,同時(shí)請(qǐng)求的 URL 也是使用的 Spring MVC 提供的注解。

測(cè)試類(lèi)如下所示:

運(yùn)行結(jié)果如下:

可以看到這里是通過(guò) @Autowired 注入剛剛定義的接口的,然后就可以直接使用其來(lái)發(fā)起 HTTP 請(qǐng)求了,使用是不是很方便、簡(jiǎn)潔。

四、探索Feign

從上面第一個(gè)原生使用的例子可以看到,只是定了接口并沒(méi)有具體的實(shí)現(xiàn)類(lèi),但是卻可以在測(cè)試類(lèi)中直接調(diào)用接口的方法來(lái)完成接口的調(diào)用,我們知道在 Java 里面接口是無(wú)法直接進(jìn)行使用的,因此可以大膽猜測(cè)是 Feign 在背后默默生成了接口的代理實(shí)現(xiàn)類(lèi),也可以驗(yàn)證一下,只需在剛剛的測(cè)試類(lèi) debug 一下看看接口實(shí)際使用的是什么實(shí)現(xiàn)類(lèi):

從 debug 結(jié)果可知,框架生成了接口的代理實(shí)現(xiàn)類(lèi) HardCodedTarget 的對(duì)象 $Proxy14 來(lái)完成接口請(qǐng)求調(diào)用,和剛剛的猜測(cè)一致。Feign 主要是封裝了 HTTP 請(qǐng)求調(diào)用,其整體架構(gòu)如下:

測(cè)試類(lèi)代碼里面只在 GitHub github = Feign.builder().target(GitHub.class, "https://api.github.com"); 用到了 Feign 框架的功能,所以我們選擇從這里來(lái)深入源碼,點(diǎn)擊進(jìn)入發(fā)現(xiàn)是 Feign 抽象類(lèi)提供的方法,同樣我們知道抽象類(lèi)也是無(wú)法進(jìn)行初始化的,所以肯定是有子類(lèi)的,如果你剛剛有仔細(xì)觀察上面的 debug 代碼的話(huà),可以發(fā)現(xiàn)有一個(gè) ReflectiveFeign 類(lèi),這個(gè)類(lèi)就是抽象類(lèi) Feign 的子類(lèi)了。抽象類(lèi) feign.Feign 的部分源碼如下:

public abstract class Feign {
    
  ...  

  public static Builder builder() {
    return new Builder();
  }

  public abstract <T> T newInstance(Target<T> target);

  public static class Builder {

    ...

    private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    private Contract contract = new Contract.Default();
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    private boolean forceDecoding = false;
    private List<Capability> capabilities = new ArrayList<>();

    // 設(shè)置輸入打印日志級(jí)別
    public Builder logLevel(Logger.Level logLevel) {
      this.logLevel = logLevel;
      return this;
    }

    // 設(shè)置接口方法注解處理器(契約) 
    public Builder contract(Contract contract) {
      this.contract = contract;
      return this;
    }

    // 設(shè)置使用的 Client(默認(rèn)使用 JDK 的 HttpURLConnection)
    public Builder client(Client client) {
      this.client = client;
      return this;
    }

    // 設(shè)置重試器
    public Builder retryer(Retryer retryer) {
      this.retryer = retryer;
      return this;
    }

    // 設(shè)置請(qǐng)求編碼器 
    public Builder encoder(Encoder encoder) {
      this.encoder = encoder;
      return this;
    }

    // 設(shè)置響應(yīng)解碼器
    public Builder decoder(Decoder decoder) {
      this.decoder = decoder;
      return this;
    }

    // 設(shè)置 404 返回結(jié)果解碼器
    public Builder decode404() {
      this.decode404 = true;
      return this;
    }

    // 設(shè)置錯(cuò)誤解碼器
    public Builder errorDecoder(ErrorDecoder errorDecoder) {
      this.errorDecoder = errorDecoder;
      return this;
    }

    // 設(shè)置請(qǐng)求攔截器
    public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
      this.requestInterceptors.clear();
      for (RequestInterceptor requestInterceptor : requestInterceptors) {
        this.requestInterceptors.add(requestInterceptor);
      }
      return this;
    }

    public <T> T target(Class<T> apiType, String url) {
      return target(new HardCodedTarget<T>(apiType, url));
    }

    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

  }

  ...

}

可以看到在方法 public T target(Class apiType, String url) 中直接創(chuàng)建了 HardCodedTarget 對(duì)象出來(lái),這個(gè)對(duì)象也是上面 debug 看到的對(duì)象。再繼續(xù)深入,就來(lái)到了 feign.Feign 的 newInstance(Target target) 的方法了,是個(gè)抽象方法,其實(shí)現(xiàn)在子類(lèi) ReflectiveFeign 中,這個(gè)方法就是接口代理實(shí)現(xiàn)生成的地方,下面通過(guò)源碼來(lái)看看實(shí)現(xiàn)邏輯是怎樣的:

public class ReflectiveFeign extends Feign {

  ...  

  private final ParseHandlersByName targetToHandlersByName;
  private final InvocationHandlerFactory factory;
  private final QueryMapEncoder queryMapEncoder;

  ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
      QueryMapEncoder queryMapEncoder) {
    this.targetToHandlersByName = targetToHandlersByName;
    this.factory = factory;
    this.queryMapEncoder = queryMapEncoder;
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T newInstance(Target<T> target) {
    // <類(lèi)名#方法簽名, MethodHandler>,key 是通過(guò) feign.Feign.configKey(Class targetType, Method method) 生成的
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    // 將 Map<String, MethodHandler> 轉(zhuǎn)換為  Map<Method, MethodHandler> 方便調(diào)用
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    // 默認(rèn)方法處理器
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      // 跳過(guò) Object 類(lèi)定于的方法  
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        // 默認(rèn)方法(接口聲明的默認(rèn)方法)使用默認(rèn)的方法處理器  
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        // 接口正常聲明的方法(e.g. GitHub.listContributors(String, String))  
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }

    // 生成 Feign 封裝的 InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 基于 JDK 動(dòng)態(tài)代理生成接口的代理類(lèi)(e.g. Github 接口)
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

...

}

總體流程就是在方法 T newInstance(Target target) 生成一個(gè)含有 FeignInvocationHandler 的代理對(duì)象,F(xiàn)eignInvocationHandler 對(duì)象會(huì)持有 Map<Method, MethodHandler> map,代理對(duì)象調(diào)用的時(shí)候進(jìn)入 FeignInvocationHandler#invoke 方法,根據(jù)調(diào)用的方法來(lái)獲取對(duì)應(yīng) MethodHandler,然后再 MethodHandler 完成對(duì)方法的處理(處理 HTTP 請(qǐng)求等)。

下面再深入 MethodHandler,看看是如何完成對(duì)方法 HTTP 請(qǐng)求處理的,MethodHandler 是一個(gè)接口定義在 feign.InvocationHandlerFactory 接口中(P.S. 基礎(chǔ)知識(shí)點(diǎn),接口是可以在內(nèi)部定義內(nèi)部接口的哦),有兩個(gè)實(shí)現(xiàn)類(lèi)分別為 DefaultMethodHandler 和 SynchronousMethodHandler,第一個(gè) DefaultMethodHandler 用來(lái)處理接口的默認(rèn)方法,第二個(gè)是用來(lái)處理正常的接口方法的,一般情況下都是由該類(lèi)來(lái)處理的。

final class SynchronousMethodHandler implements MethodHandler {

  ...

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    // 獲取 RequestTemplate 將請(qǐng)求參數(shù)封裝成請(qǐng)求模板  
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    // 請(qǐng)求重試器
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // 執(zhí)行請(qǐng)求并解碼后返回  
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          // 發(fā)生重試異常則進(jìn)行重試處理  
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 從請(qǐng)求模板 RequestTemplate 構(gòu)造請(qǐng)求參數(shù)對(duì)象 Request  
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      // 通過(guò) client(Apache HttpComponnets、HttpURLConnection OkHttp 等)執(zhí)行 HTTP 請(qǐng)求調(diào)用,默認(rèn)是 HttpURLConnection 
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    if (decoder != null)
      // 對(duì)返回結(jié)果進(jìn)行解碼操作
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

...

}

至此,F(xiàn)eign 的核心實(shí)現(xiàn)流程介紹完畢,從代碼上看 feign.SynchronousMethodHandler 的操作相對(duì)比較簡(jiǎn)單,主要是通過(guò) client 完成請(qǐng)求,對(duì)響應(yīng)進(jìn)行解碼以及異常處理操作,整體流程如下:

五、總結(jié)

Feign 通過(guò)給我們定義的目標(biāo)接口(比如例子中的 GitHub)生成一個(gè) HardCodedTarget 類(lèi)型的代理對(duì)象,由 JDK 動(dòng)態(tài)代理實(shí)現(xiàn),生成代理的時(shí)候會(huì)根據(jù)注解來(lái)生成一個(gè)對(duì)應(yīng)的 Map<Method, MethodHandler>,這個(gè) Map 被 InvocationHandler 持有,接口方法調(diào)用的時(shí)候,進(jìn)入 InvocationHandler 的 invoke 方法(為什么會(huì)進(jìn)入這里?JDK 動(dòng)態(tài)代理的基礎(chǔ)知識(shí))。

然后根據(jù)調(diào)用的方法從 Map<Method, MethodHandler> 獲取對(duì)應(yīng)的 MethodHandler,然后通過(guò) MethodHandler 根據(jù)指定的 client 來(lái)完成對(duì)應(yīng)處理, MethodHandler 中的實(shí)現(xiàn)類(lèi) DefaultMethodHandler 處理默認(rèn)方法(接口的默認(rèn)方法)的請(qǐng)求處理的,SynchronousMethodHandler 實(shí)現(xiàn)類(lèi)是完成其它方法的 HTTP 請(qǐng)求的實(shí)現(xiàn),這就是 Feign 的主要核心流程。以上是 Feign 框架實(shí)現(xiàn)的核心流程介紹。

以上就是詳解Feign的實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Feign原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用springboot對(duì)linux進(jìn)行操控的方法示例

    使用springboot對(duì)linux進(jìn)行操控的方法示例

    這篇文章主要介紹了使用springboot對(duì)linux進(jìn)行操控的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 關(guān)于Java雙大括號(hào){{}}的具體使用

    關(guān)于Java雙大括號(hào){{}}的具體使用

    本文主要介紹了關(guān)于Java雙大括號(hào){{}}的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Spring Boot Web應(yīng)用程序配置詳解

    Spring Boot Web應(yīng)用程序配置詳解

    這篇文章主要介紹了Spring Boot Web應(yīng)用程序配置詳解,本文中將介紹一些Web應(yīng)用程序最常用的配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • SpringBoot @CompentScan excludeFilters配置無(wú)效的解決方案

    SpringBoot @CompentScan excludeFilters配置無(wú)效的解決方案

    這篇文章主要介紹了SpringBoot @CompentScan excludeFilters配置無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java ArrayAdapter用法案例詳解

    Java ArrayAdapter用法案例詳解

    這篇文章主要介紹了Java ArrayAdapter用法案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • java代碼實(shí)現(xiàn)斗地主發(fā)牌功能

    java代碼實(shí)現(xiàn)斗地主發(fā)牌功能

    這篇文章主要介紹了java實(shí)現(xiàn)斗地主發(fā)牌功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • Java?C++題解eetcode940不同的子序列?II

    Java?C++題解eetcode940不同的子序列?II

    這篇文章主要為大家介紹了Java?C++題解eetcode940不同的子序列?II實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • selenium + ChromeDriver安裝及使用方法

    selenium + ChromeDriver安裝及使用方法

    這篇文章主要介紹了selenium + ChromeDriver安裝及使用方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-06-06
  • Java實(shí)現(xiàn)經(jīng)典大富翁游戲的示例詳解

    Java實(shí)現(xiàn)經(jīng)典大富翁游戲的示例詳解

    大富翁,又名地產(chǎn)大亨。是一種多人策略圖版游戲。參與者分得游戲金錢(qián),憑運(yùn)氣(擲骰子)及交易策略,買(mǎi)地、建樓以賺取租金。本文將通過(guò)Java實(shí)現(xiàn)這一經(jīng)典游戲,感興趣的可以跟隨小編一起學(xué)習(xí)一下
    2022-02-02
  • 詳解Mybatis的分頁(yè)插件

    詳解Mybatis的分頁(yè)插件

    這篇文章主要介紹了詳解Mybatis的分頁(yè)插件,在 Mybatis中,如何對(duì)數(shù)據(jù)進(jìn)行分頁(yè)是一個(gè)非常常見(jiàn)的問(wèn)題,現(xiàn)在,我們可以通過(guò)使用 Mybatis 的分頁(yè)插件來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)的分頁(yè),需要的朋友可以參考下
    2023-05-05

最新評(píng)論