Spring Could Feign 璁捐鍘熺悊

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Could Feign 璁捐鍘熺悊相关的知识,希望对你有一定的参考价值。

鏍囩锛?a href='http://www.mamicode.com/so/1/%e5%8e%9f%e7%90%86' title='鍘熺悊'>鍘熺悊   messages   绾跨▼   鏃堕棿闂撮殧   瀵硅薄   pre   http   defaults   return   

浠€涔堟槸Feign锛?/h2>

Feign 鐨勮嫳鏂囪〃鎰忎负鈥滃亣瑁咃紝浼锛屽彉褰⑩€濓紝 鏄竴涓猦ttp璇锋眰璋冪敤鐨勮交閲忕骇妗嗘灦锛屽彲浠ヤ互Java鎺ュ彛娉ㄨВ鐨勬柟寮忚皟鐢℉ttp璇锋眰锛岃€屼笉鐢ㄥ儚Java涓€氳繃灏佽HTTP璇锋眰鎶ユ枃鐨勬柟寮忕洿鎺ヨ皟鐢ㄣ€侳eign閫氳繃澶勭悊娉ㄨВ锛屽皢璇锋眰妯℃澘鍖栵紝褰撳疄闄呰皟鐢ㄧ殑鏃跺€欙紝浼犲叆鍙傛暟锛屾牴鎹弬鏁板啀搴旂敤鍒拌姹備笂锛岃繘鑰岃浆鍖栨垚鐪熸鐨勮姹傦紝杩欑璇锋眰鐩稿鑰岃█姣旇緝鐩磋銆?br> Feign琚箍娉涘簲鐢ㄥ湪Spring Cloud 鐨勮В鍐虫柟妗堜腑锛屾槸瀛︿範鍩轰簬Spring Cloud 寰湇鍔℃灦鏋勪笉鍙垨缂虹殑閲嶈缁勪欢銆?br> 寮€婧愰」鐩湴鍧€锛?br>https://github.com/OpenFeign/feign

Feign瑙e喅浜嗕粈涔堥棶棰橈紵

灏佽浜咹ttp璋冪敤娴佺▼锛屾洿閫傚悎闈㈠悜鎺ュ彛鍖栫殑鍙樻垚涔犳儻
鍦ㄦ湇鍔¤皟鐢ㄧ殑鍦烘櫙涓紝鎴戜滑缁忓父璋冪敤鍩轰簬Http鍗忚鐨勬湇鍔★紝鑰屾垜浠粡甯镐娇鐢ㄥ埌鐨勬鏋跺彲鑳芥湁HttpURLConnection銆丄pache HttpComponnets銆丱kHttp3 銆丯etty绛夌瓑锛岃繖浜涙鏋跺湪鍩轰簬鑷韩鐨勪笓娉ㄧ偣鎻愪緵浜嗚嚜韬壒鎬с€傝€屼粠瑙掕壊鍒掑垎涓婃潵鐪嬶紝浠栦滑鐨勮亴鑳芥槸涓€鑷寸殑鎻愪緵Http璋冪敤鏈嶅姟銆傚叿浣撴祦绋嬪涓嬶細


鎶€鏈浘鐗? data-original-src=
image.png

Feign鏄浣曡璁$殑锛?/h2>

鎶€鏈浘鐗? data-original-src=
image.png

PHASE 1. 鍩轰簬闈㈠悜鎺ュ彛鐨勫姩鎬佷唬鐞嗘柟寮忕敓鎴愬疄鐜扮被

鍦ㄤ娇鐢╢eign 鏃讹紝浼氬畾涔夊搴旂殑鎺ュ彛绫伙紝鍦ㄦ帴鍙g被涓婁娇鐢℉ttp鐩稿叧鐨勬敞瑙o紝鏍囪瘑HTTP璇锋眰鍙傛暟淇℃伅,濡備笅鎵€绀猴細

interface GitHub 
  @RequestLine("GET /repos/owner/repo/contributors")
  List<Contributor> 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<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) 
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    
  

鍦‵eign 搴曞眰锛岄€氳繃鍩轰簬闈㈠悜鎺ュ彛鐨勫姩鎬佷唬鐞嗘柟寮忕敓鎴愬疄鐜扮被锛屽皢璇锋眰璋冪敤濮旀墭鍒板姩鎬佷唬鐞嗗疄鐜扮被锛屽熀鏈師鐞嗗涓嬫墍绀猴細



鎶€鏈浘鐗? data-original-src=
image.png
 public class ReflectiveFeign extends Feign
  ///鐪佺暐閮ㄥ垎浠g爜
  @Override
  public <T> T newInstance(Target<T> target) 
    //鏍规嵁鎺ュ彛绫诲拰Contract鍗忚瑙f瀽鏂瑰紡锛岃В鏋愭帴鍙g被涓婄殑鏂规硶鍜屾敞瑙o紝杞崲鎴愬唴閮ㄧ殑MethodHandler澶勭悊鏂瑰紡
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    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 涓烘帴鍙g被鍒涘缓鍔ㄦ€佸疄鐜帮紝灏嗘墍鏈夌殑璇锋眰杞崲缁橧nvocationHandler 澶勭悊銆?/span>
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]target.type(), handler);

    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) 
      defaultMethodHandler.bindTo(proxy);
    
    return proxy;
  
  //鐪佺暐閮ㄥ垎浠g爜

PHASE 2. 鏍规嵁Contract鍗忚瑙勫垯锛岃В鏋愭帴鍙g被鐨勬敞瑙d俊鎭紝瑙f瀽鎴愬唴閮ㄨ〃鐜帮細


鎶€鏈浘鐗? data-original-src=
image.png

Feign 瀹氫箟浜嗚浆鎹㈠崗璁紝瀹氫箟濡備笅锛?/p>

/**
 * 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.
   * 浼犲叆鎺ュ彛瀹氫箟锛岃В鏋愭垚鐩稿簲鐨勬柟娉曞唴閮ㄥ厓鏁版嵁琛ㄧず
   * @param targetType @link feign.Target#type() type of the Feign interface.
   */
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);

榛樿Contract 瀹炵幇

Feign 榛樿鏈変竴濂楄嚜宸辩殑鍗忚瑙勮寖锛岃瀹氫簡涓€浜涙敞瑙o紝鍙互鏄犲皠鎴愬搴旂殑Http璇锋眰锛屽瀹樻柟鐨勪竴涓緥瀛愶細

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


涓婅堪鐨勪緥瀛愪腑锛屽皾璇曡皟鐢℅itHub.getContributors("foo","myrepo")鐨勭殑鏃跺€欙紝浼氳浆鎹㈡垚濡備笅鐨凥TTP璇锋眰锛?/p>

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

Feign 榛樿鐨勫崗璁鑼?/p>

娉ㄨВ鎺ュ彛Target浣跨敤璇存槑
@RequestLine鏂规硶涓?/td>瀹氫箟HttpMethod 鍜?UriTemplate. UriTemplate 涓娇鐢?code> 鍖呰9鐨勮〃杈惧紡锛屽彲浠ラ€氳繃鍦ㄦ柟娉曞弬鏁颁笂浣跨敤@Param 鑷姩娉ㄥ叆
@Param鏂规硶鍙傛暟瀹氫箟妯℃澘鍙橀噺锛屾ā鏉垮彉閲忕殑鍊煎彲浠ヤ娇鐢ㄥ悕绉扮殑鏂瑰紡浣跨敤妯℃澘娉ㄥ叆瑙f瀽
@Headers绫讳笂鎴栬€呮柟娉曚笂瀹氫箟澶撮儴妯℃澘鍙橀噺锛屼娇鐢ˊParam 娉ㄨВ鎻愪緵鍙傛暟鍊肩殑娉ㄥ叆銆傚鏋滆娉ㄨВ娣诲姞鍦ㄦ帴鍙g被涓婏紝鍒欐墍鏈夌殑璇锋眰閮戒細鎼哄甫瀵瑰簲鐨凥eader淇℃伅锛涘鏋滃湪鏂规硶涓婏紝鍒欏彧浼氭坊鍔犲埌瀵瑰簲鐨勬柟娉曡姹備笂
@QueryMap鏂规硶涓?/td>瀹氫箟涓€涓敭鍊煎鎴栬€?pojo锛屽弬鏁板€煎皢浼氳杞崲鎴怳RL涓婄殑 query 瀛楃涓蹭笂
@HeaderMap鏂规硶涓?/td>瀹氫箟涓€涓狧eaderMap, 涓?UrlTemplate 鍜孒eaderTemplate 绫诲瀷锛屽彲浠ヤ娇鐢ˊParam 娉ㄨВ鎻愪緵鍙傛暟鍊?/td>

鍏蜂綋FeignContract 鏄浣曡В鏋愮殑锛屼笉鍦ㄦ湰鏂囩殑浠嬬粛鑼冨洿鍐咃紝璇︽儏璇峰弬鑰冧唬鐮侊細
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Contract.java

鍩轰簬Spring MVC鐨勫崗璁鑼僑pringMvcContract:

褰撳墠Spring Cloud 寰湇鍔¤В鍐虫柟妗堜腑锛屼负浜嗛檷浣庡涔犳垚鏈紝閲囩敤浜哠pring MVC鐨勯儴鍒嗘敞瑙f潵瀹屾垚 璇锋眰鍗忚瑙f瀽锛屼篃灏辨槸璇?锛屽啓瀹㈡埛绔姹傛帴鍙e拰鍍忓啓鏈嶅姟绔唬鐮佷竴鏍凤細瀹㈡埛绔拰鏈嶅姟绔彲浠ラ€氳繃SDK鐨勬柟寮忚繘琛岀害瀹氾紝瀹㈡埛绔彧闇€瑕佸紩鍏ユ湇鍔$鍙戝竷鐨凷DK API锛屽氨鍙互浣跨敤闈㈠悜鎺ュ彛鐨勭紪鐮佹柟寮忓鎺ユ湇鍔★細



鎶€鏈浘鐗? data-original-src=
image.png

鎴戜滑鍥㈤槦鍐呴儴灏辨槸鎸夌収杩欑鎬濊矾锛岀粨鍚圫pring Boot Starter 鐨勭壒鎬э紝瀹氫箟浜嗘湇鍔$starter,
鏈嶅姟娑堣垂鑰呭湪浣跨敤鐨勬椂鍊欙紝鍙渶瑕佸紩鍏tarter锛屽氨鍙互璋冪敤鏈嶅姟銆傝繖涓瘮杈冮€傚悎骞冲彴鏃犲叧鎬э紝鎺ュ彛鎶借薄鍑烘潵鐨勫ソ澶勫氨鏄彲浠ユ牴鎹湇鍔¤皟鐢ㄥ疄鐜版柟寮忚嚜鏈夊垏鎹細

  1. 鍙互鍩轰簬绠€鍗曠殑Http鏈嶅姟璋冪敤锛?/li>
  2. 鍙互鍩轰簬Spring Cloud 寰湇鍔℃灦鏋勮皟鐢紱
  3. 鍙互鍩轰簬Dubbo SOA鏈嶅姟娌荤悊

杩欑妯″紡姣旇緝閫傚悎鍦⊿aSS娣峰悎杞欢鏈嶅姟鐨勬ā寮忎笅鑷湁鍒囨崲锛屾牴鎹鎴风殑纭欢鑳藉姏閫夋嫨鍚堥€傜殑鏂瑰紡閮ㄧ讲锛屼篃鍙互鍩轰簬鑷韩鐨勬湇鍔¢泦缇ら儴缃插井鏈嶅姟

鑷充簬Spring Cloud 鏄浣曞疄鐜?鍗忚瑙f瀽鐨勶紝鍙弬鑰冧唬鐮侊細
https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java

褰撶劧锛岀洰鍓嶇殑Spring MVC鐨勬敞瑙e苟涓嶆槸鍙互瀹屽叏浣跨敤鐨勶紝鏈変竴浜涙敞瑙e苟涓嶆敮鎸?濡?code>@GetMapping,@PutMapping 绛夛紝浠呮敮鎸佷娇鐢?code>@RequestMapping 绛夛紝鍙﹀娉ㄨВ缁ф壙鎬ф柟闈篃鏈変簺闂锛涘叿浣撻檺鍒剁粏鑺傦紝姣忎釜鐗堟湰鑳戒細鏈変簺鍑哄叆锛屽彲浠ュ弬鑰冧笂杩扮殑浠g爜瀹炵幇锛屾瘮杈冪畝鍗曘€?/p>

Spring Cloud 娌℃湁鍩轰簬Spring MVC 鍏ㄩ儴娉ㄨВ鏉ュ仛Feign 瀹㈡埛绔敞瑙e崗璁В鏋愶紝涓汉璁や负杩欎釜鏄竴涓笉灏忕殑鍧戙€傚湪鍒氬叆鎵婼pring Cloud 鐨勬椂鍊欙紝灏辩鍒拌繖涓棶棰樸€傚悗鏉ユ槸娣卞叆浠g爜鎵嶈В鍐崇殑.... 杩欎釜搴旇鏈変汉鍐欎簡澧炲己绫绘潵澶勭悊锛屾殏涓斾笉琛紝鍏圡ARK涓€涓嬶紝鏄竴涓紑婧愪唬鐮佺粌鎵嬬殑濂芥満浼氥€?/p>

PHASE 3. 鍩轰簬 RequestBean锛屽姩鎬佺敓鎴怰equest

鏍规嵁浼犲叆鐨凚ean瀵硅薄鍜屾敞瑙d俊鎭紝浠庝腑鎻愬彇鍑虹浉搴旂殑鍊硷紝鏉ユ瀯閫燞ttp Request 瀵硅薄锛?/p>


鎶€鏈浘鐗? data-original-src=
image.png
PHASE 4. 浣跨敤Encoder 灏咮ean杞崲鎴?Http鎶ユ枃姝f枃锛堟秷鎭В鏋愬拰杞爜閫昏緫锛?/h5>

Feign 鏈€缁堜細灏嗚姹傝浆鎹㈡垚Http 娑堟伅鍙戦€佸嚭鍘伙紝浼犲叆鐨勮姹傚璞℃渶缁堜細瑙f瀽鎴愭秷鎭綋锛屽涓嬫墍绀猴細



鎶€鏈浘鐗? data-original-src=
image.png

鍦ㄦ帴鍙e畾涔変笂Feign鍋氱殑姣旇緝绠€鍗曪紝鎶借薄鍑轰簡Encoder 鍜宒ecoder 鎺ュ彛锛?/p>

public interface Encoder 
  /** Type literal for @code Map<String, ?>, 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.
   *  灏嗗疄浣撳璞¤浆鎹㈡垚Http璇锋眰鐨勬秷鎭鏂囦腑
   * @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.
   *  浠嶳esponse 涓彁鍙朒ttp娑堟伅姝f枃锛岄€氳繃鎺ュ彛绫诲0鏄庣殑杩斿洖绫诲瀷锛屾秷鎭嚜鍔ㄨ閰?
   * @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 鏈変互涓嬪疄鐜帮細

Encoder/ Decoder 瀹炵幇璇存槑
JacksonEncoder锛孞acksonDecoder鍩轰簬 Jackson 鏍煎紡鐨勬寔涔呭寲杞崲鍗忚
GsonEncoder锛孏sonDecoder鍩轰簬Google GSON 鏍煎紡鐨勬寔涔呭寲杞崲鍗忚
SaxEncoder锛孲axDecoder鍩轰簬XML 鏍煎紡鐨凷ax 搴撴寔涔呭寲杞崲鍗忚
JAXBEncoder锛孞AXBDecoder鍩轰簬XML 鏍煎紡鐨凧AXB 搴撴寔涔呭寲杞崲鍗忚
ResponseEntityEncoder锛孯esponseEntityDecoderSpring MVC 鍩轰簬 ResponseEntity< T > 杩斿洖鏍煎紡鐨勮浆鎹㈠崗璁?/td>
SpringEncoder锛孲pringDecoder鍩轰簬Spring MVC HttpMessageConverters 涓€濂楁満鍒跺疄鐜扮殑杞崲鍗忚 ,搴旂敤浜嶴pring Cloud 浣撶郴涓?/td>
PHASE 5. 鎷︽埅鍣ㄨ礋璐e璇锋眰鍜岃繑鍥炶繘琛岃楗板鐞?/h5>

鍦ㄨ姹傝浆鎹㈢殑杩囩▼涓紝Feign 鎶借薄鍑烘潵浜嗘嫤鎴櫒鎺ュ彛锛岀敤浜庣敤鎴疯嚜瀹氫箟瀵硅姹傜殑鎿嶄綔锛?/p>

public interface RequestInterceptor 

  /**
   * 鍙互鍦ㄦ瀯閫燫equestTemplate 璇锋眰鏃讹紝澧炲姞鎴栬€呬慨鏀笻eader, Method, Body 绛変俊鎭?
   * Called for every request. Add data using methods on the supplied @link RequestTemplate.
   */
  void apply(RequestTemplate template);

姣斿锛屽鏋滃笇鏈汬ttp娑堟伅浼犻€掕繃绋嬩腑琚帇缂╋紝鍙互瀹氫箟涓€涓姹傛嫤鎴櫒锛?/p>

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) 
        //  鍦℉eader 澶撮儴娣诲姞鐩稿簲鐨勬暟鎹俊鎭?/span>
        addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
                HttpEncoding.DEFLATE_ENCODING);
    

PHASE 6. 鏃ュ織璁板綍

鍦ㄥ彂閫佸拰鎺ユ敹璇锋眰鐨勬椂鍊欙紝Feign瀹氫箟浜嗙粺涓€鐨勬棩蹇楅棬闈㈡潵杈撳嚭鏃ュ織淇℃伅 , 骞朵笖灏嗘棩蹇楃殑杈撳嚭瀹氫箟浜嗗洓涓瓑绾э細

绾у埆璇存槑
NONE涓嶅仛浠讳綍璁板綍
BASIC鍙褰曡緭鍑篐ttp 鏂规硶鍚嶇О銆佽姹俇RL銆佽繑鍥炵姸鎬佺爜鍜屾墽琛屾椂闂?/td>
HEADERS璁板綍杈撳嚭Http 鏂规硶鍚嶇О銆佽姹俇RL銆佽繑鍥炵姸鎬佺爜鍜屾墽琛屾椂闂?鍜?Header 淇℃伅
FULL璁板綍Request 鍜孯esponse鐨凥eader锛孊ody鍜屼竴浜涜姹傚厓鏁版嵁
public abstract class Logger 

  protected static String methodTag(String configKey) 
    return new StringBuilder().append(鈥榌鈥?/span>).append(configKey.substring(0, configKey.indexOf(鈥?鈥?/span>)))
        .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 . 鍩轰簬閲嶈瘯鍣ㄥ彂閫丠TTP璇锋眰

Feign 鍐呯疆浜嗕竴涓噸璇曞櫒锛屽綋HTTP璇锋眰鍑虹幇IO寮傚父鏃讹紝Feign浼氭湁涓€涓渶澶у皾璇曟鏁板彂閫佽姹傦紝浠ヤ笅鏄疐eign鏍稿績
浠g爜閫昏緫锛?/p>

final class SynchronousMethodHandler implements MethodHandler 

  // 鐪佺暐閮ㄥ垎浠g爜

  @Override
  public Object invoke(Object[] argv) throws Throwable 
   //鏍规嵁杈撳叆鍙傛暟锛屾瀯閫燞ttp 璇锋眰銆?/span>
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 鍏嬮殕鍑轰竴浠介噸璇曞櫒
    Retryer retryer = this.retryer.clone();
    // 灏濊瘯鏈€澶ф鏁帮紝濡傛灉涓棿鏈夌粨鏋滐紝鐩存帴杩斿洖
    while (true) 
      try 
        return executeAndDecode(template);
       catch (RetryableException e) 
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) 
          logger.logRetry(metadata.configKey(), logLevel);
        
        continue;
      
    
  

閲嶈瘯鍣ㄦ湁濡備笅鍑犱釜鎺у埗鍙傛暟锛?/p>

閲嶈瘯鍙傛暟璇存槑榛樿鍊?/th>
period鍒濆閲嶈瘯鏃堕棿闂撮殧锛屽綋璇锋眰澶辫触鍚庯紝閲嶈瘯鍣ㄥ皢浼氭殏鍋?鍒濆鏃堕棿闂撮殧(绾跨▼ sleep 鐨勬柟寮?鍚庡啀寮€濮嬶紝閬垮厤寮哄埛璇锋眰锛屾氮璐规€ц兘100ms
maxPeriod褰撹姹傝繛缁け璐ユ椂锛岄噸璇曠殑鏃堕棿闂撮殧灏嗘寜鐓э細long interval = (long) (period * Math.pow(1.5, attempt - 1)); 璁$畻锛屾寜鐓х瓑姣斾緥鏂瑰紡寤堕暱锛屼絾鏄渶澶ч棿闅旀椂闂翠负 maxPeriod, 璁剧疆姝ゅ€艰兘澶熼伩鍏?閲嶈瘯娆℃暟杩囧鐨勬儏鍐典笅鎵ц鍛ㄦ湡澶暱1000ms
maxAttempts鏈€澶ч噸璇曟鏁?/td>5

鍏蜂綋鐨勪唬鐮佸疄鐜板彲鍙傝€冿細
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Retryer.java

PHASE 8. 鍙戦€丠ttp璇锋眰

Feign 鐪熸鍙戦€丠TTP璇锋眰鏄鎵樼粰 feign.Client 鏉ュ仛鐨勶細

public interface Client 

  /**
   * Executes a request against its @link Request#url() url and returns a response.
   *  鎵цHttp璇锋眰锛屽苟杩斿洖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 榛樿搴曞眰閫氳繃JDK 鐨?java.net.HttpURLConnection 瀹炵幇浜?code>feign.Client鎺ュ彛绫?鍦ㄦ瘡娆″彂閫佽姹傜殑鏃跺€欙紝閮戒細鍒涘缓鏂扮殑HttpURLConnection 閾炬帴锛岃繖涔熷氨鏄负浠€涔堥粯璁ゆ儏鍐典笅Feign鐨勬€ц兘寰堝樊鐨勫師鍥犮€傚彲浠ラ€氳繃鎷撳睍璇ユ帴鍙o紝浣跨敤Apache HttpClient 鎴栬€匫kHttp3绛夊熀浜庤繛鎺ユ睜鐨勯珮鎬ц兘Http瀹㈡埛绔紝鎴戜滑椤圭洰鍐呴儴浣跨敤鐨勫氨鏄疧kHttp3浣滀负Http 瀹㈡埛绔€?/p>

濡備笅鏄疐eign 鐨勯粯璁ゅ疄鐜帮紝渚涘弬鑰冿細

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<String> 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<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
      for (Map.Entry<String, List<String>> 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 鏁翠綋妗嗘灦闈炲父灏忓阀锛屽湪澶勭悊璇锋眰杞崲鍜屾秷鎭В鏋愮殑杩囩▼涓紝鍩烘湰涓婃病浠€涔堟椂闂存秷鑰椼€傜湡姝e奖鍝嶆€ц兘鐨勶紝鏄鐞咹ttp璇锋眰鐨勭幆鑺傘€?br> 濡備笂鎵€杩帮紝鐢变簬榛樿鎯呭喌涓嬶紝Feign閲囩敤鐨勬槸JDK鐨?code>HttpURLConnection,鎵€浠ユ暣浣撴€ц兘骞朵笉楂橈紝鍒氬紑濮嬫帴瑙pring Cloud 鐨勫悓瀛︼紝濡傛灉娌℃敞鎰忚繖浜涚粏鑺傦紝鍙兘浼氬Spring Cloud 鏈夊緢澶х殑鍋忚銆?br> 鎴戜滑椤圭洰鍐呴儴浣跨敤鐨勬槸OkHttp3 浣滀负杩炴帴瀹㈡埛绔€?/p>



浣滆€咃細浜﹀北鏈
閾炬帴锛歨ttps://www.jianshu.com/p/8c7b92b4396c
鏉ユ簮锛氱畝涔?br>绠€涔﹁憲浣滄潈褰掍綔鑰呮墍鏈夛紝浠讳綍褰㈠紡鐨勮浆杞介兘璇疯仈绯讳綔鑰呰幏寰楁巿鏉冨苟娉ㄦ槑鍑哄銆?/div>

以上是关于Spring Could Feign 璁捐鍘熺悊的主要内容,如果未能解决你的问题,请参考以下文章

1. feign鐨勪娇鐢ㄥ強鍘熺悊

鐮佸啘鎵嬭 | Spring WebFlux瀹炴垬浠ュ強鍘熺悊娴呮瀽

鏁扮粍鍧嶅鍘熺悊

log4j鍩烘湰鍘熺悊

鎵嬪啓Vue2.0婧愮爜锛堝洓锛?娓叉煋鏇存柊鍘熺悊

HTTP 2.0 鍘熺悊璇︾粏鍒嗘瀽