Feign的工作原理

chendom發表於2019-05-04

Feign的工作原理

  • 主程式入口新增了@EnableFeignClients註解開啟對FeignClient掃描載入處理。根據Feign Client的開發規範,定義介面並加@FeignClientd註解。

  • 當程式啟動時,回進行包掃描,掃描所有@FeignClients的註解的類,並且講這些資訊注入Spring IOC容器中,當定義的的Feign介面中的方法唄呼叫時,通過JDK的代理方式,來生成具體的RequestTemplate.當生成代理時,Feign會為每個介面方法建立一個RequestTemplate。當生成代理時,Feign會為每個介面方法建立一個RequestTemplate物件,改物件封裝可HTTP請求需要的全部資訊,如請求引數名,請求方法等資訊都是在這個過程中確定的。

  • 然後RequestTemplate生成Request,然後把Request交給Client去處理,這裡指的時Client可以時JDK原生的URLConnection,Apache的HttpClient,也可以時OKhttp,最後Client被封裝到LoadBalanceClient類,這個類結合Ribbon負載均衡發器服務之間的呼叫。

Feign註解剖析

@FeignClient註解主要被@Target({ElementType.TYPE})修飾,表示該註解主要使用在介面上。它具備瞭如下的屬性:

  • name:指定FeignClient的名稱,如果使用了Ribbon,name就作為微服務的名稱,用於服務發現。

  • url:url一般用於除錯,可以指定@FeignClient呼叫的地址。

  • decode404: 當大聲404錯誤時,如果該欄位為true,會呼叫decoder進行解碼,否則丟擲FeignException.

  • configuration:Feign配置類,累哦總動脈或Feign的Encoder,Decoder,LogLevel,Contract。

  • fallback:定義容錯的處理類,當呼叫遠端介面失敗或者超時時,會呼叫對應的介面的容錯邏輯,fallback指定的類必須實現@Feign標記的介面。

  • fallbacjFactory:工廠類,用於生成fallback類例項,通過這個屬性可以實現每個介面通用的容錯邏輯們介紹重複的程式碼。

  • path:定義當前FeignClient的統一字首。

Feign開啟GZIP壓縮

Spring Cloud Feign支援對請求和響應的進行GZIP壓縮,以提高通訊效率。 在yml檔案需要如下的配置

feign:
  compression:
    request:
      enabled: true #開請求壓縮
      mimeTypes: #媒體型別 text/xml,application/xml,application/json
      minRequestSize: 2048 #最小的請求大小
    response:
      enabled: true #開啟響應的壓縮
複製程式碼

需要注意的是,在採用了壓縮之後,需要使用二級制的方式進行資料傳遞,所有返回值就需要使用 ResponseEntity<byte[]> 接收.

@FeignClient(name = "github-client",url = "https://api.github.com",configuration = HelloFeignServiceConfig.class)
public interface HelloFeignService {

    /*
    這個返回型別如果採用了壓縮,那麼就是二進位制的方式,就需要使用ResponseEntity<byte[]>作為返回值
     */
    @RequestMapping(value = "/search/repositories",method = RequestMethod.GET)
    ResponseEntity<byte[]> searchRepositories(@RequestParam("q")String parameter);
}
複製程式碼

Feign Client開啟日誌

需要在註解類新增配置的類:

@FeignClient(name = "github-client",url = "https://api.github.com",configuration = HelloFeignServiceConfig.class)
複製程式碼

註解類的程式碼如下:

@Configuration
public class HelloFeignServiceConfig {

    /**
     *
     * Logger.Level 的具體級別如下:
         NONE:不記錄任何資訊
         BASIC:僅記錄請求方法、URL以及響應狀態碼和執行時間
         HEADERS:除了記錄 BASIC級別的資訊外,還會記錄請求和響應的頭資訊
         FULL:記錄所有請求與響應的明細,包括頭資訊、請求體、後設資料
     * @return
     */
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

}
複製程式碼

Feign的超時設定

Feign的呼叫分兩層,即Ribbon層的呼叫和Hystrix的呼叫,高版本的Hystrix預設是關閉的。

Feign的工作原理

  • 如果出現上述的錯誤,那麼就需要新增如下的配置

    #請求處理的超時時間 ribbon.ReadTimeout: 12000 #請求連結超時時間 ribbon.ConnectionTimeout: 30000

  • 如果開啟了Hystrix,Hystrix的超時報錯資訊如下:

Feign的工作原理

此時可以新增如下配置:

hystrix:
  command:
    default:
      circuitBreaker:
        sleepWindowInMilliseconds: 30000
        requestVolumeThreshold: 50
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: SEMAPHORE
          semaphore:
            maxConcurrentRequests: 50
          thread:
            timeoutInMilliseconds: 100000
複製程式碼

Feign的Post和Get的多引數傳遞

在SpringMVC中可以使用Post或者Get請求輕易的請求到對應的介面並且講引數繫結到POJO,但是Feign並沒有全部實現SpringMVC的功能,如果使用GET請求到介面,就無法將引數繫結到POJO,但是可以使用以下的幾種方式實現相應的功能。

  • 將POJO內的欄位作為一個個的欄位解除安裝介面上

  • 將引數程式設計一個map

  • POJO使用@RequestBody修飾,但是這個違反Restful的原則

以下的程式碼將Feign的請求攔截下來,將引數進行處理之後統一的程式設計map

@Component
public class FeignRequestInterceptor implements RequestInterceptor{
    @Autowired
    private ObjectMapper objectMapper;



    @Override
    public void apply(RequestTemplate requestTemplate) {
        // feign 不支援 GET 方法傳 POJO, json body轉query
        if (requestTemplate.method().equalsIgnoreCase("GET") && requestTemplate.body()!=null){
            try {
                JsonNode jsonNode = objectMapper.readTree(requestTemplate.body());
                requestTemplate.body(null);
                Map<String,Collection<String>> queries = new HashMap<>();
                buildQuery(jsonNode,"",queries);
                requestTemplate.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) {   // 葉子節點
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) {   // 陣列節點
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else {  // 根節點
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }

}
複製程式碼

除了上面的方法之外還可以使用venus-cloud-feign這個依賴,該依賴實現了GET請求的引數Map包裝,無需再去自己寫。依賴如下:

<!-- https://mvnrepository.com/artifact/cn.springcloud.feign/venus-cloud-feign-core -->
<dependency>
    <groupId>cn.springcloud.feign</groupId>
    <artifactId>venus-cloud-feign-core</artifactId>
    <version>1.0.0</version>
</dependency>
複製程式碼

github的地址如下:github.com/SpringCloud…

相關文章