成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Spring Cloud Feign設(shè)計(jì)原理

陳江龍 / 2548人閱讀

摘要:而從角色劃分上來(lái)看,他們的職能是一致的提供調(diào)用服務(wù)。沒(méi)有基于全部注解來(lái)做客戶(hù)端注解協(xié)議解析,個(gè)人認(rèn)為這個(gè)是一個(gè)不小的坑。真正影響性能的,是處理請(qǐng)求的環(huán)節(jié)。我們項(xiàng)目?jī)?nèi)部使用的是作為連接客戶(hù)端。

什么是Feign?

Feign 的英文表意為“假裝,偽裝,變形”, 是一個(gè)http請(qǐng)求調(diào)用的輕量級(jí)框架,可以以Java接口注解的方式調(diào)用Http請(qǐng)求,而不用像Java中通過(guò)封裝HTTP請(qǐng)求報(bào)文的方式直接調(diào)用。Feign通過(guò)處理注解,將請(qǐng)求模板化,當(dāng)實(shí)際調(diào)用的時(shí)候,傳入?yún)?shù),根據(jù)參數(shù)再應(yīng)用到請(qǐng)求上,進(jìn)而轉(zhuǎn)化成真正的請(qǐng)求,這種請(qǐng)求相對(duì)而言比較直觀。
Feign被廣泛應(yīng)用在Spring Cloud 的解決方案中,是學(xué)習(xí)基于Spring Cloud 微服務(wù)架構(gòu)不可或缺的重要組件。

Feign解決了什么問(wèn)題?

封裝了Http調(diào)用流程,更適合面向接口化的變成習(xí)慣
在服務(wù)調(diào)用的場(chǎng)景中,我們經(jīng)常調(diào)用基于Http協(xié)議的服務(wù),而我們經(jīng)常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,這些框架在基于自身的專(zhuān)注點(diǎn)提供了自身特性。而從角色劃分上來(lái)看,他們的職能是一致的提供Http調(diào)用服務(wù)。具體流程如下:

Feign是如何設(shè)計(jì)的?

PHASE 1. 基于面向接口的動(dòng)態(tài)代理方式生成實(shí)現(xiàn)類(lèi)

在使用feign 時(shí),會(huì)定義對(duì)應(yīng)的接口類(lèi),在接口類(lèi)上使用Http相關(guān)的注解,標(biāo)識(shí)HTTP請(qǐng)求參數(shù)信息,如下所示:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List contributors(@Param("owner") String owner, @Param("repo") String repo);
}

public static class Contributor {
  String login;
  int contributions;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

在Feign 底層,通過(guò)基于面向接口的動(dòng)態(tài)代理方式生成實(shí)現(xiàn)類(lèi),將請(qǐng)求調(diào)用委托到動(dòng)態(tài)代理實(shí)現(xiàn)類(lèi),基本原理如下所示:

 public class ReflectiveFeign extends Feign{
  ///省略部分代碼
  @Override
  public  T newInstance(Target target) {
    //根據(jù)接口類(lèi)和Contract協(xié)議解析方式,解析接口類(lèi)上的方法和注解,轉(zhuǎn)換成內(nèi)部的MethodHandler處理方式
    Map nameToHandler = targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 基于Proxy.newProxyInstance 為接口類(lèi)創(chuàng)建動(dòng)態(tài)實(shí)現(xiàn),將所有的請(qǐng)求轉(zhuǎn)換給InvocationHandler 處理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
  //省略部分代碼
PHASE 2. 根據(jù)Contract協(xié)議規(guī)則,解析接口類(lèi)的注解信息,解析成內(nèi)部表現(xiàn):

Feign 定義了轉(zhuǎn)換協(xié)議,定義如下:

/**
 * Defines what annotations and values are valid on interfaces.
 */
public interface Contract {

  /**
   * Called to parse the methods in the class that are linked to HTTP requests.
   * 傳入接口定義,解析成相應(yīng)的方法內(nèi)部元數(shù)據(jù)表示
   * @param targetType {@link feign.Target#type() type} of the Feign interface.
   */
  // TODO: break this and correct spelling at some point
  List parseAndValidatateMetadata(Class targetType);
}
默認(rèn)Contract 實(shí)現(xiàn)

Feign 默認(rèn)有一套自己的協(xié)議規(guī)范,規(guī)定了一些注解,可以映射成對(duì)應(yīng)的Http請(qǐng)求,如官方的一個(gè)例子:

public interface GitHub {
  
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List getContributors(@Param("owner") String owner, @Param("repo") String repository);
  
  class Contributor {
    String login;
    int contributions;
  }
}

上述的例子中,嘗試調(diào)用GitHub.getContributors("foo","myrepo")的的時(shí)候,會(huì)轉(zhuǎn)換成如下的HTTP請(qǐng)求:

GET /repos/foo/myrepo/contributors
HOST XXXX.XXX.XXX

Feign 默認(rèn)的協(xié)議規(guī)范

注解 接口Target 使用說(shuō)明
@RequestLine 方法上 定義HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表達(dá)式,可以通過(guò)在方法參數(shù)上使 用@Param 自動(dòng)注入
@Param 方法參數(shù) 定義模板變量,模板變量的值可以使用名稱(chēng)的方式使用模板注入解析
@Headers 類(lèi)上或者方法上 定義頭部模板變量,使用@Param注解提供參數(shù)值的注入。如果該注解添加在接口類(lèi)上,則所有的請(qǐng)求都會(huì)攜帶對(duì)應(yīng)的Header信息;如果在方法上,則只會(huì)添加到對(duì)應(yīng)的方法請(qǐng)求上
@QueryMap 方法上 定義一個(gè)鍵值對(duì)或者pojo,參數(shù)值將會(huì)轉(zhuǎn)換成URL上的查詢(xún)字符串上
@HeaderMap 方法上 定義一個(gè)HeaderMap,與UrlTemplate和HeaderTemplate類(lèi)型,可以使用@Param注解提供參數(shù)值

具體FeignContract 是如何解析的,不在本文的介紹范圍內(nèi),請(qǐng)自行查找。

基于Spring MVC的協(xié)議規(guī)范SpringMvcContract:

當(dāng)前Spring Cloud 微服務(wù)解決方案中,為了降低學(xué)習(xí)成本,采用了Spring MVC的部分注解來(lái)完成 請(qǐng)求協(xié)議解析,也就是說(shuō) ,寫(xiě)客戶(hù)端請(qǐng)求接口和像寫(xiě)服務(wù)端代碼一樣:客戶(hù)端和服務(wù)端可以通過(guò)SDK的方式進(jìn)行約定,客戶(hù)端只需要引入服務(wù)端發(fā)布的SDK API,就可以使用面向接口的編碼方式對(duì)接服務(wù):

我們團(tuán)隊(duì)內(nèi)部就是按照這種思路,結(jié)合Spring Boot Starter 的特性,定義了服務(wù)端starter,
服務(wù)消費(fèi)者在使用的時(shí)候,只需要引入Starter,就可以調(diào)用服務(wù)。這個(gè)比較適合平臺(tái)無(wú)關(guān)性,接口抽象出來(lái)的好處就是可以根據(jù)服務(wù)調(diào)用實(shí)現(xiàn)方式自有切換:

可以基于簡(jiǎn)單的Http服務(wù)調(diào)用;

可以基于Spring Cloud 微服務(wù)架構(gòu)調(diào)用;

可以基于Dubbo SOA服務(wù)治理

這種模式比較適合在SaSS混合軟件服務(wù)的模式下自有切換,根據(jù)客戶(hù)的硬件能力選擇合適的方式部署,也可以基于自身的服務(wù)集群部署微服務(wù)

當(dāng)然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,僅支持使用@RequestMapping 等,另外注解繼承性方面也有些問(wèn)題;具體限制細(xì)節(jié),每個(gè)版本能會(huì)有些出入,可以參考上述的代碼實(shí)現(xiàn),比較簡(jiǎn)單。

Spring Cloud 沒(méi)有基于Spring MVC 全部注解來(lái)做Feign 客戶(hù)端注解協(xié)議解析,個(gè)人認(rèn)為這個(gè)是一個(gè)不小的坑。在剛?cè)胧諷pring Cloud 的時(shí)候,就碰到這個(gè)問(wèn)題。后來(lái)是深入代碼才解決的.... 這個(gè)應(yīng)該有人寫(xiě)了增強(qiáng)類(lèi)來(lái)處理,暫且不表,先MARK一下,是一個(gè)開(kāi)源代碼練手的好機(jī)會(huì)。
PHASE 3. 基于 RequestBean,動(dòng)態(tài)生成Request

根據(jù)傳入的Bean對(duì)象和注解信息,從中提取出相應(yīng)的值,來(lái)構(gòu)造Http Request 對(duì)象:

PHASE 4. 使用Encoder 將Bean轉(zhuǎn)換成 Http報(bào)文正文(消息解析和轉(zhuǎn)碼邏輯)

Feign 最終會(huì)將請(qǐng)求轉(zhuǎn)換成Http 消息發(fā)送出去,傳入的請(qǐng)求對(duì)象最終會(huì)解析成消息體,如下所示:

在接口定義上Feign做的比較簡(jiǎn)單,抽象出了Encoder 和decoder 接口:

public interface Encoder {
  /** Type literal for {@code Map}, indicating the object to encode is a form. */
  Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;

  /**
   * Converts objects to an appropriate representation in the template.
   *  將實(shí)體對(duì)象轉(zhuǎn)換成Http請(qǐng)求的消息正文中
   * @param object   what to encode as the request body.
   * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}
   *                 indicates form encoding.
   * @param template the request template to populate.
   * @throws EncodeException when encoding failed due to a checked exception.
   */
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;

  /**
   * Default implementation of {@code Encoder}.
   */
  class Default implements Encoder {

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
        template.body(object.toString());
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if (object != null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass()));
      }
    }
  }
}
public interface Decoder {

  /**
   * Decodes an http response into an object corresponding to its {@link
   * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap
   * exceptions, please do so via {@link DecodeException}.
   *  從Response 中提取Http消息正文,通過(guò)接口類(lèi)聲明的返回類(lèi)型,消息自動(dòng)裝配
   * @param response the response to decode 
   * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of
   *                 the method corresponding to this {@code response}.
   * @return instance of {@code type}
   * @throws IOException     will be propagated safely to the caller.
   * @throws DecodeException when decoding failed due to a checked exception besides IOException.
   * @throws FeignException  when decoding succeeds, but conveys the operation failed.
   */
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;

  /** Default implementation of {@code Decoder}. */
  public class Default extends StringDecoder {

    @Override
    public Object decode(Response response, Type type) throws IOException {
      if (response.status() == 404) return Util.emptyValueOf(type);
      if (response.body() == null) return null;
      if (byte[].class.equals(type)) {
        return Util.toByteArray(response.body().asInputStream());
      }
      return super.decode(response, type);
    }
  }
}

目前Feign 有以下實(shí)現(xiàn):

Encoder/ Decoder 實(shí)現(xiàn) 說(shuō)明
JacksonEncoder,JacksonDecoder 基于 Jackson 格式的持久化轉(zhuǎn)換協(xié)議
GsonEncoder,GsonDecoder 基于Google GSON 格式的持久化轉(zhuǎn)換協(xié)議
SaxEncoder,SaxDecoder 基于XML 格式的Sax 庫(kù)持久化轉(zhuǎn)換協(xié)議
JAXBEncoder,JAXBDecoder 基于XML 格式的JAXB 庫(kù)持久化轉(zhuǎn)換協(xié)議
ResponseEntityEncoder,ResponseEntityDecoder Spring MVC 基于 ResponseEntity< T > 返回格式的轉(zhuǎn)換協(xié)議
SpringEncoder,SpringDecoder 基于Spring MVC HttpMessageConverters 一套機(jī)制實(shí)現(xiàn)的轉(zhuǎn)換協(xié)議 ,應(yīng)用于Spring Cloud 體系中
PHASE 5. 攔截器負(fù)責(zé)對(duì)請(qǐng)求和返回進(jìn)行裝飾處理

在請(qǐng)求轉(zhuǎn)換的過(guò)程中,F(xiàn)eign 抽象出來(lái)了攔截器接口,用于用戶(hù)自定義對(duì)請(qǐng)求的操作:

public interface RequestInterceptor {

  /**
   * 可以在構(gòu)造RequestTemplate 請(qǐng)求時(shí),增加或者修改Header, Method, Body 等信息
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

比如,如果希望Http消息傳遞過(guò)程中被壓縮,可以定義一個(gè)請(qǐng)求攔截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {

    /**
     * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
     *
     * @param properties the encoding properties
     */
    protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
        super(properties);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void apply(RequestTemplate template) {
        //  在Header 頭部添加相應(yīng)的數(shù)據(jù)信息
        addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
                HttpEncoding.DEFLATE_ENCODING);
    }
}
PHASE 6. 日志記錄

在發(fā)送和接收請(qǐng)求的時(shí)候,F(xiàn)eign定義了統(tǒng)一的日志門(mén)面來(lái)輸出日志信息 , 并且將日志的輸出定義了四個(gè)等級(jí):

級(jí)別 說(shuō)明
NONE 不做任何記錄
BASIC 只記錄輸出Http 方法名稱(chēng)、請(qǐng)求URL、返回狀態(tài)碼和執(zhí)行時(shí)間
HEADERS 記錄輸出Http 方法名稱(chēng)、請(qǐng)求URL、返回狀態(tài)碼和執(zhí)行時(shí)間 和 Header 信息
FULL 記錄Request 和Response的Header,Body和一些請(qǐng)求元數(shù)據(jù)
public abstract class Logger {

  protected static String methodTag(String configKey) {
    return new StringBuilder().append("[").append(configKey.substring(0, configKey.indexOf("(")))
        .append("] ").toString();
  }

  /**
   * Override to log requests and responses using your own implementation. Messages will be http
   * request and response text.
   *
   * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)}
   * @param format    {@link java.util.Formatter format string}
   * @param args      arguments applied to {@code format}
   */
  protected abstract void log(String configKey, String format, Object... args);

  protected void logRequest(String configKey, Level logLevel, Request request) {
    log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

      for (String field : request.headers().keySet()) {
        for (String value : valuesOrEmpty(request.headers(), field)) {
          log(configKey, "%s: %s", field, value);
        }
      }

      int bodyLength = 0;
      if (request.body() != null) {
        bodyLength = request.body().length;
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          String
              bodyText =
              request.charset() != null ? new String(request.body(), request.charset()) : null;
          log(configKey, ""); // CRLF
          log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
        }
      }
      log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
    }
  }

  protected void logRetry(String configKey, Level logLevel) {
    log(configKey, "---> RETRYING");
  }

  protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
                                            long elapsedTime) throws IOException {
    String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
        " " + response.reason() : "";
    int status = response.status();
    log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

      for (String field : response.headers().keySet()) {
        for (String value : valuesOrEmpty(response.headers(), field)) {
          log(configKey, "%s: %s", field, value);
        }
      }

      int bodyLength = 0;
      if (response.body() != null && !(status == 204 || status == 205)) {
        // HTTP 204 No Content "...response MUST NOT include a message-body"
        // HTTP 205 Reset Content "...response MUST NOT include an entity"
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          log(configKey, ""); // CRLF
        }
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        bodyLength = bodyData.length;
        if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
          log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
        }
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
        return response.toBuilder().body(bodyData).build();
      } else {
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
      }
    }
    return response;
  }

  protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
    log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
        elapsedTime);
    if (logLevel.ordinal() >= Level.FULL.ordinal()) {
      StringWriter sw = new StringWriter();
      ioe.printStackTrace(new PrintWriter(sw));
      log(configKey, sw.toString());
      log(configKey, "<--- END ERROR");
    }
    return ioe;
  }
PHASE 7 . 基于重試器發(fā)送HTTP請(qǐng)求

Feign 內(nèi)置了一個(gè)重試器,當(dāng)HTTP請(qǐng)求出現(xiàn)IO異常時(shí),F(xiàn)eign會(huì)有一個(gè)最大嘗試次數(shù)發(fā)送請(qǐng)求,以下是Feign核心
代碼邏輯:

final class SynchronousMethodHandler implements MethodHandler {

  // 省略部分代碼

  @Override
  public Object invoke(Object[] argv) throws Throwable {
   //根據(jù)輸入?yún)?shù),構(gòu)造Http 請(qǐng)求。
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 克隆出一份重試器
    Retryer retryer = this.retryer.clone();
    // 嘗試最大次數(shù),如果中間有結(jié)果,直接返回
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

重試器有如下幾個(gè)控制參數(shù):

重試參數(shù) 說(shuō)明 默認(rèn)值
period foo 100ms
maxPeriod 當(dāng)請(qǐng)求連續(xù)失敗時(shí),重試的時(shí)間間隔將按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 計(jì)算,按照等比例方式延長(zhǎng),但是最大間隔時(shí)間為 maxPeriod, 設(shè)置此值能夠避免 重試次數(shù)過(guò)多的情況下執(zhí)行周期太長(zhǎng) 1000ms
maxAttempts 最大重試次數(shù) 5
PHASE 8. 發(fā)送Http請(qǐng)求

Feign 真正發(fā)送HTTP請(qǐng)求是委托給 feign.Client 來(lái)做的:

public interface Client {

  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   *  執(zhí)行Http請(qǐng)求,并返回Response
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
   */
  Response execute(Request request, Options options) throws IOException;
  }

Feign 默認(rèn)底層通過(guò)JDK 的 java.net.HttpURLConnection 實(shí)現(xiàn)了feign.Client接口類(lèi),在每次發(fā)送請(qǐng)求的時(shí)候,都會(huì)創(chuàng)建新的HttpURLConnection 鏈接,這也就是為什么默認(rèn)情況下Feign的性能很差的原因。可以通過(guò)拓展該接口,使用Apache HttpClient 或者OkHttp3等基于連接池的高性能Http客戶(hù)端,我們項(xiàng)目?jī)?nèi)部使用的就是OkHttp3作為Http 客戶(hù)端。

如下是Feign 的默認(rèn)實(shí)現(xiàn),供參考:

public static class Default implements Client {

    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

    /**
     * Null parameters imply platform defaults.
     */
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;
    }

    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
    }

    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final HttpURLConnection
          connection =
          (HttpURLConnection) new URL(request.url()).openConnection();
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
          sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
          sslCon.setHostnameVerifier(hostnameVerifier);
        }
      }
      connection.setConnectTimeout(options.connectTimeoutMillis());
      connection.setReadTimeout(options.readTimeoutMillis());
      connection.setAllowUserInteraction(false);
      connection.setInstanceFollowRedirects(true);
      connection.setRequestMethod(request.method());

      Collection contentEncodingValues = request.headers().get(CONTENT_ENCODING);
      boolean
          gzipEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
      boolean
          deflateEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

      boolean hasAcceptHeader = false;
      Integer contentLength = null;
      for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
          hasAcceptHeader = true;
        }
        for (String value : request.headers().get(field)) {
          if (field.equals(CONTENT_LENGTH)) {
            if (!gzipEncodedRequest && !deflateEncodedRequest) {
              contentLength = Integer.valueOf(value);
              connection.addRequestProperty(field, value);
            }
          } else {
            connection.addRequestProperty(field, value);
          }
        }
      }
      // Some servers choke on the default accept string.
      if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");
      }

      if (request.body() != null) {
        if (contentLength != null) {
          connection.setFixedLengthStreamingMode(contentLength);
        } else {
          connection.setChunkedStreamingMode(8196);
        }
        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
          out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
          out = new DeflaterOutputStream(out);
        }
        try {
          out.write(request.body());
        } finally {
          try {
            out.close();
          } catch (IOException suppressed) { // NOPMD
          }
        }
      }
      return connection;
    }

    Response convertResponse(HttpURLConnection connection) throws IOException {
      int status = connection.getResponseCode();
      String reason = connection.getResponseMessage();

      if (status < 0) {
        throw new IOException(format("Invalid status(%s) executing %s %s", status,
            connection.getRequestMethod(), connection.getURL()));
      }

      Map> headers = new LinkedHashMap>();
      for (Map.Entry> field : connection.getHeaderFields().entrySet()) {
        // response message
        if (field.getKey() != null) {
          headers.put(field.getKey(), field.getValue());
        }
      }

      Integer length = connection.getContentLength();
      if (length == -1) {
        length = null;
      }
      InputStream stream;
      if (status >= 400) {
        stream = connection.getErrorStream();
      } else {
        stream = connection.getInputStream();
      }
      return Response.builder()
              .status(status)
              .reason(reason)
              .headers(headers)
              .body(stream, length)
              .build();
    }
  }
Feign 的性能怎么樣?

Feign 整體框架非常小巧,在處理請(qǐng)求轉(zhuǎn)換和消息解析的過(guò)程中,基本上沒(méi)什么時(shí)間消耗。真正影響性能的,是處理Http請(qǐng)求的環(huán)節(jié)。
如上所述,由于默認(rèn)情況下,F(xiàn)eign采用的是JDK的HttpURLConnection,所以整體性能并不高,剛開(kāi)始接觸Spring Cloud 的同學(xué),如果沒(méi)注意這些細(xì)節(jié),可能會(huì)對(duì)Spring Cloud 有很大的偏見(jiàn)。
我們項(xiàng)目?jī)?nèi)部使用的是OkHttp3 作為連接客戶(hù)端。

原文:https://www.jianshu.com/p/8c7...

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

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

相關(guān)文章

  • 拜托!面試請(qǐng)不要再問(wèn)我Spring Cloud底層原理!

    摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線(xiàn)程池里的線(xiàn)程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...

    wums 評(píng)論0 收藏0
  • 拜托!面試請(qǐng)不要再問(wèn)我Spring Cloud底層原理!

    摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線(xiàn)程池里的線(xiàn)程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...

    wangjuntytl 評(píng)論0 收藏0
  • spring-cloud-feign源碼深度解析

    摘要:內(nèi)部使用了的動(dòng)態(tài)代理為目標(biāo)接口生成了一個(gè)動(dòng)態(tài)代理類(lèi),這里會(huì)生成一個(gè)動(dòng)態(tài)代理原理統(tǒng)一的方法攔截器,同時(shí)為接口的每個(gè)方法生成一個(gè)攔截器,并解析方法上的元數(shù)據(jù),生成一個(gè)請(qǐng)求模板。的核心源碼解析到此結(jié)束了,不知道是否對(duì)您有無(wú)幫助,可留言跟我交流。 Feign是一個(gè)聲明式的Web服務(wù)客戶(hù)端。這使得Web服務(wù)客戶(hù)端的寫(xiě)入更加方便 要使用Feign創(chuàng)建一個(gè)界面并對(duì)其進(jìn)行注釋。它具有可插拔注釋支持,包...

    vibiu 評(píng)論0 收藏0
  • Spring Cloud Alibaba Sentinel 整合 Feign設(shè)計(jì)實(shí)現(xiàn)

    摘要:作用跟一致跟屬性作用一致給設(shè)置注解絕對(duì)路徑,用于替換服務(wù)名。在服務(wù)名或與之間默認(rèn)是,表示當(dāng)前這個(gè)生成的是否是。內(nèi)部的能獲取服務(wù)名信息,的實(shí)現(xiàn)類(lèi)能拿到對(duì)應(yīng)的請(qǐng)求路徑信息。很不幸,這個(gè)類(lèi)也是包級(jí)別的類(lèi)。整合的代碼目前已經(jīng)在倉(cāng)庫(kù)上,但是沒(méi)未發(fā)版。 作者 | Spring Cloud Alibaba 高級(jí)開(kāi)發(fā)工程師洛夜來(lái)自公眾號(hào)阿里巴巴中間件投稿 前段時(shí)間 Hystrix 宣布不再維護(hù)之后(H...

    OldPanda 評(píng)論0 收藏0
  • Spring Cloud Alibaba Sentinel對(duì)Feign的支持

    摘要:得到得到類(lèi)得到類(lèi)得到調(diào)用的服務(wù)名稱(chēng)檢查和屬性省略部分代碼中的方法里面進(jìn)行熔斷限流的處理。在的方法中進(jìn)行的包裝。 Spring Cloud Alibaba Sentinel 除了對(duì) RestTemplate 做了支持,同樣對(duì)于 Feign 也做了支持,如果我們要從 Hystrix 切換到 Sentinel 是非常方便的,下面來(lái)介紹下如何對(duì) Feign 的支持以及實(shí)現(xiàn)原理。 集成 Feig...

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

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

0條評(píng)論

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