中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Cloud中Feign的實現原理是什么

發布時間:2021-06-29 16:11:59 來源:億速云 閱讀:415 作者:chen 欄目:開發技術

本篇內容主要講解“Spring Cloud中Feign的實現原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Spring Cloud中Feign的實現原理是什么”吧!

目錄
  • 一、什么是Feign

  • 二、為什么用Feign

  • 三、實例

    • 3.1、原生使用方式

    • 3.2、結合 Spring Cloud 使用方式

  • 四、探索Feign

    • 五、總結

      一、什么是Feign

      Feign 是?個 HTTP 請求的輕量級客戶端框架。通過 接口 + 注解的方式發起 HTTP 請求調用,面向接口編程,而不是像 Java 中通過封裝 HTTP 請求報文的方式直接調用。服務消費方拿到服務提供方的接?,然后像調?本地接??法?樣去調?,實際發出的是遠程的請求。讓我們更加便捷和優雅的去調?基于 HTTP 的 API,被?泛應?在 Spring Cloud 的解決?案中。開源項目地址: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 的首要目標就是減少 HTTP 調用的復雜性。在微服務調用的場景中,我們調用很多時候都是基于 HTTP 協議的服務,如果服務調用只使用提供 HTTP 調用服務的 HTTP Client 框架(e.g. Apache HttpComponnets、HttpURLConnection OkHttp 等),我們需要關注哪些問題呢?

      Spring Cloud中Feign的實現原理是什么

      相比這些 HTTP 請求框架,Feign 封裝了 HTTP 請求調用的流程,而且會強制使用者去養成面向接口編程的習慣(因為 Feign 本身就是要面向接口)。

      三、實例

      3.1、原生使用方式

      以獲取 Feign 的 GitHub 開源項目的 Contributors 為例,原生方式使用 Feign 步驟有如下三步(這里以使用 Gradle 進行依賴管理的項目為例):

      第一步: 引入相關依賴:implementation 'io.github.openfeign:feign-core:11.0'

      在項目的 build.gradle 文件的依賴聲明處 dependencies 添加該依賴聲明即可。

      第二步: 聲明 HTTP 請求接口

      使用 Java 的接口和 Feign 的原生注解 @RequestLine 聲明 HTTP 請求接口,從這里就可以看到 Feign 給使用者封裝了 HTTP 的調用細節,極大的減少了 HTTP 調用的復雜性,只要定義接口即可。

      Spring Cloud中Feign的實現原理是什么

      第三步: 配置初始化 Feign 客戶端

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

      Spring Cloud中Feign的實現原理是什么

      通過定義接口,使用注解的方式描述接口的信息,就可以發起接口調用。最后請求結果如下:

      Spring Cloud中Feign的實現原理是什么

      3.2、結合 Spring Cloud 使用方式

      同樣還是以獲取 Feign 的 GitHub 開源項目的 Contributors 為例,結合 Spring Cloud 的使用方式有如下三步:

      第一步: 引入相關 starter 依賴:org.springframework.cloud:spring-cloud-starter-openfeign

      在項目的 build.gradle 文件的依賴聲明處 dependencies 添加該依賴聲明即可。

      第二步: 在項目的啟動類 XXXApplication 上添加 @EnableFeignClients 注解啟用 Feign 客戶端功能。

      Spring Cloud中Feign的實現原理是什么

      第三步: 創建 HTTP 調用接口,并添加聲明 @FeignClient 注解。

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

      Spring Cloud中Feign的實現原理是什么

      測試類如下所示:

      Spring Cloud中Feign的實現原理是什么

      運行結果如下:

      Spring Cloud中Feign的實現原理是什么

      可以看到這里是通過 @Autowired 注入剛剛定義的接口的,然后就可以直接使用其來發起 HTTP 請求了,使用是不是很方便、簡潔。

      四、探索Feign

      從上面第一個原生使用的例子可以看到,只是定了接口并沒有具體的實現類,但是卻可以在測試類中直接調用接口的方法來完成接口的調用,我們知道在 Java 里面接口是無法直接進行使用的,因此可以大膽猜測是 Feign 在背后默默生成了接口的代理實現類,也可以驗證一下,只需在剛剛的測試類 debug 一下看看接口實際使用的是什么實現類:

      Spring Cloud中Feign的實現原理是什么

      從 debug 結果可知,框架生成了接口的代理實現類 HardCodedTarget 的對象 $Proxy14 來完成接口請求調用,和剛剛的猜測一致。Feign 主要是封裝了 HTTP 請求調用,其整體架構如下:

      Spring Cloud中Feign的實現原理是什么

      測試類代碼里面只在 GitHub github = Feign.builder().target(GitHub.class, "https://api.github.com"); 用到了 Feign 框架的功能,所以我們選擇從這里來深入源碼,點擊進入發現是 Feign 抽象類提供的方法,同樣我們知道抽象類也是無法進行初始化的,所以肯定是有子類的,如果你剛剛有仔細觀察上面的 debug 代碼的話,可以發現有一個 ReflectiveFeign 類,這個類就是抽象類 Feign 的子類了。抽象類 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<>();
      
          // 設置輸入打印日志級別
          public Builder logLevel(Logger.Level logLevel) {
            this.logLevel = logLevel;
            return this;
          }
      
          // 設置接口方法注解處理器(契約) 
          public Builder contract(Contract contract) {
            this.contract = contract;
            return this;
          }
      
          // 設置使用的 Client(默認使用 JDK 的 HttpURLConnection)
          public Builder client(Client client) {
            this.client = client;
            return this;
          }
      
          // 設置重試器
          public Builder retryer(Retryer retryer) {
            this.retryer = retryer;
            return this;
          }
      
          // 設置請求編碼器 
          public Builder encoder(Encoder encoder) {
            this.encoder = encoder;
            return this;
          }
      
          // 設置響應解碼器
          public Builder decoder(Decoder decoder) {
            this.decoder = decoder;
            return this;
          }
      
          // 設置 404 返回結果解碼器
          public Builder decode404() {
            this.decode404 = true;
            return this;
          }
      
          // 設置錯誤解碼器
          public Builder errorDecoder(ErrorDecoder errorDecoder) {
            this.errorDecoder = errorDecoder;
            return this;
          }
      
          // 設置請求攔截器
          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);
          }
      
        }
      
        ...
      
      }

      可以看到在方法 publicT target(ClassapiType, String url) 中直接創建了 HardCodedTarget 對象出來,這個對象也是上面 debug 看到的對象。再繼續深入,就來到了 feign.Feign 的 newInstance(Targettarget) 的方法了,是個抽象方法,其實現在子類 ReflectiveFeign 中,這個方法就是接口代理實現生成的地方,下面通過源碼來看看實現邏輯是怎樣的:

      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) {
          // <類名#方法簽名, MethodHandler>,key 是通過 feign.Feign.configKey(Class targetType, Method method) 生成的
          Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
          // 將 Map<String, MethodHandler> 轉換為  Map<Method, MethodHandler> 方便調用
          Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
          // 默認方法處理器
          List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
      
          for (Method method : target.type().getMethods()) {
            // 跳過 Object 類定于的方法  
            if (method.getDeclaringClass() == Object.class) {
              continue;
            } else if (Util.isDefault(method)) {
              // 默認方法(接口聲明的默認方法)使用默認的方法處理器  
              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 動態代理生成接口的代理類(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(Targettarget) 生成一個含有 FeignInvocationHandler 的代理對象,FeignInvocationHandler 對象會持有 Map<Method, MethodHandler> map,代理對象調用的時候進入 FeignInvocationHandler#invoke 方法,根據調用的方法來獲取對應 MethodHandler,然后再 MethodHandler 完成對方法的處理(處理 HTTP 請求等)。

      下面再深入 MethodHandler,看看是如何完成對方法 HTTP 請求處理的,MethodHandler 是一個接口定義在 feign.InvocationHandlerFactory 接口中(P.S. 基礎知識點,接口是可以在內部定義內部接口的哦),有兩個實現類分別為 DefaultMethodHandler 和 SynchronousMethodHandler,第一個 DefaultMethodHandler 用來處理接口的默認方法,第二個是用來處理正常的接口方法的,一般情況下都是由該類來處理的。

      final class SynchronousMethodHandler implements MethodHandler {
      
        ...
      
        @Override
        public Object invoke(Object[] argv) throws Throwable {
          // 獲取 RequestTemplate 將請求參數封裝成請求模板  
          RequestTemplate template = buildTemplateFromArgs.create(argv);
          Options options = findOptions(argv);
          // 請求重試器
          Retryer retryer = this.retryer.clone();
          while (true) {
            try {
              // 執行請求并解碼后返回  
              return executeAndDecode(template, options);
            } catch (RetryableException e) {
              try {
                // 發生重試異常則進行重試處理  
                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 {
          // 從請求模板 RequestTemplate 構造請求參數對象 Request  
          Request request = targetRequest(template);
      
          if (logLevel != Logger.Level.NONE) {
            logger.logRequest(metadata.configKey(), logLevel, request);
          }
      
          Response response;
          long start = System.nanoTime();
          try {
            // 通過 client(Apache HttpComponnets、HttpURLConnection OkHttp 等)執行 HTTP 請求調用,默認是 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)
            // 對返回結果進行解碼操作
            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;
          }
        }
      
      ...
      
      }

      至此,Feign 的核心實現流程介紹完畢,從代碼上看 feign.SynchronousMethodHandler 的操作相對比較簡單,主要是通過 client 完成請求,對響應進行解碼以及異常處理操作,整體流程如下:

      Spring Cloud中Feign的實現原理是什么

      五、總結

      Feign 通過給我們定義的目標接口(比如例子中的 GitHub)生成一個 HardCodedTarget 類型的代理對象,由 JDK 動態代理實現,生成代理的時候會根據注解來生成一個對應的 Map<Method, MethodHandler>,這個 Map 被 InvocationHandler 持有,接口方法調用的時候,進入 InvocationHandler 的 invoke 方法(為什么會進入這里?JDK 動態代理的基礎知識)。

      然后根據調用的方法從 Map<Method, MethodHandler> 獲取對應的 MethodHandler,然后通過 MethodHandler 根據指定的 client 來完成對應處理, MethodHandler 中的實現類 DefaultMethodHandler 處理默認方法(接口的默認方法)的請求處理的,SynchronousMethodHandler 實現類是完成其它方法的 HTTP 請求的實現,這就是 Feign 的主要核心流程。以上是 Feign 框架實現的核心流程介紹。

      到此,相信大家對“Spring Cloud中Feign的實現原理是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

      向AI問一下細節

      免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

      AI

      昌黎县| 佛学| 都昌县| 米林县| 韶山市| 华安县| 新巴尔虎右旗| 历史| 龙里县| 顺昌县| 宜君县| 四平市| 雷州市| 荥经县| 临泉县| 桂阳县| 麻栗坡县| 顺平县| 通许县| 永平县| 合水县| 神池县| 鸡泽县| 昌吉市| 改则县| 内乡县| 根河市| 彰化县| 清水县| 宣威市| 嵊州市| 泽州县| 乌苏市| 乐业县| 台安县| 岗巴县| 金寨县| 特克斯县| 云霄县| 高台县| 永清县|