Spring Cloud(Finchley.SR1)之feign(四)

青鳥&飛魚發表於2017-11-28

1.Feign簡介

目錄

1.Feign簡介

2.使用Feign

2.1 pom.xml中新增

2.2 定義介面

2.3 啟動類

2.4 application.yml檔案

2.5 覆蓋Feign預設值,自定義FeignClient

2.6 .Feign 相關配置

3. Feign的Encoder、Decoder和ErrorDecoder

Feign的HTTP Client

總結


Feign是一個宣告式的Web服務客戶端。這使得Web服務客戶端的寫入更加方便 要使用Feign建立一個介面並對其進行註釋。它具有可插入註釋支援,包括Feign註釋和JAX-RS註釋。Feign還支援可插拔編碼器和解碼器。Spring Cloud增加了對Spring MVC註釋的支援,並使用Spring Web中預設使用的HttpMessageConverters。Spring Cloud整合Ribbon和Eureka以在使用Feign時提供負載均衡的http客戶端。

在Spring Cloud Netflix棧中,各個微服務都是以HTTP介面的形式暴露自身服務的,因此在呼叫遠端服務時就必須使用HTTP客戶端。我們可以使用JDK原生的URLConnection、Apache的Http Client、Netty的非同步HTTP Client, Spring的RestTemplate。但是,用起來最方便、最優雅的還是要屬Feign了。

Feign是一種宣告式、模板化的HTTP客戶端。在Spring Cloud中使用Feign, 我們可以做到使用HTTP請求遠端服務時能與呼叫本地方法一樣的編碼體驗,開發者完全感知不到這是遠端方法,更感知不到這是個HTTP請求。

現在為止所進行的 有Rest 服務呼叫實際上都會出現一個非常尷尬的局面,所有的資料呼叫和轉換都必須由戶自己來完成,而我們本身不擅 所有的資料呼叫和轉換都必須由戶自己來完成,我們習慣的程式設計模式是:通過介面來實現業務 長這些,我們習慣的程式設計模式是:通過介面來實現業務 ,這可以通過feign來實現,Feign = RestTempate + HttpHeader Ribbon Eureka綜合體 綜合體 = 業務介面的自動例項化。

 

2.使用Feign

2.1 pom.xml中新增

老版本用spring-cloud-starter-feign,Finchley.SR1版本的用spring-cloud-starter-openfeign

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud</artifactId>
        <groupId>com.alen</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>feign-service</artifactId>
    <dependencies>
        <!--eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!--feign支援-->
          <!--feign支援-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

2.2 定義介面

為了讓Feign知道在呼叫方法時應該向哪個地址發請求以及請求需要帶哪些引數,我們需要定義一個介面:

package com.alen.service;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 定義一個feign介面,通過@ FeignClient(“服務名”),來指定呼叫哪個服務
 **/
@FeignClient(value ="eureka-client")
public interface HelloService {

    @RequestMapping("/hello")
     //必須顯示的指定age,不顯示還不行
      String hello(@RequestParam("age") Integer age);
}

A: @FeignClient用於通知Feign元件對該介面進行代理(不需要編寫介面實現),使用者可直接通過@Autowired注入。

B: @RequestMapping表示在呼叫該方法時需要向/group/{groupId}傳送GET請求。

C: @PathVariableSpringMVC中對應註解含義相同。

Spring Cloud應用在啟動時,Feign會掃描標有@FeignClient註解的介面,生成代理,並註冊到Spring容器中。生成代理時Feign會為每個介面方法建立一個RequetTemplate物件,該物件封裝了HTTP請求需要的全部資訊,請求引數名、請求方法等資訊都是在這個過程中確定的,Feign的模板化就體現在這裡。

在本例中,我們將Feign與Eureka和Ribbon組合使用,@FeignClient(name = "ea")意為通知Feign在呼叫該介面方法時要向Eureka中查詢名為ea的服務,從而得到服務URL。

controller中呼叫這個介面

@RestController
public class HelloController {
    @Autowired
    private HelloService  helloService;
    /**
     * 通過Controller呼叫遠端服務
     * @param age
     * @return
     */
    @RequestMapping("/hello")
    public String home(@RequestParam Integer age) {
        return helloService.hello(age);
    }
}

 

2.3 啟動類

@SpringBootApplication
//通過註解@EnableEurekaClient 表明自己是一個eurekaclient.
@EnableEurekaClient
//開啟Feign的功能
@EnableFeignClients
public class App {
   public static void main(String[] args) {
      SpringApplication.run(App.class, args);
   }
}

 

2.4 application.yml檔案

server:
  port: 8014
eureka:
  instance:
    hostname: localhost
    # 發呆時間,即服務失效時間(預設為90s),就是超過15秒沒有續約就會從登錄檔中剔除
    lease-expiration-duration-in-seconds: 15
    # 心跳時間,即服務續約間隔時間(預設為30s)
    lease-renewal-interval-in-seconds: 5
  client:
   #設定eureka伺服器所在的地址,查詢服務和註冊服務都需要依賴這個地址。高可用的註冊中心時,
    #可以配置多個註冊中心,通過逗號隔開
    service-url:
     defaultZone: http://localhost:8010/eureka/ 

 

Feign的原始碼實現的過程如下:

  • 首先通過@EnableFeignCleints註解開啟FeignCleint
  • 根據Feign的規則實現介面,並加@FeignCleint註解
  • 程式啟動後,會進行包掃描,掃描所有的@ FeignCleint的註解的類,並將這些資訊注入到ioc容器中。
  • 當介面的方法被呼叫,通過jdk的代理,來生成具體的RequesTemplate
  • RequesTemplate在生成Request
  • Request交給Client去處理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
  • 最後Client被封裝到LoadBalanceClient類,這個類結合類Ribbon做到了負載均衡。

2.5 覆蓋Feign預設值,自定義FeignClient

預設的配置類為FeignClientsConfiguration,這個類在spring-cloud-netflix-core的jar包下,開啟這個類,可以發現它是一個配置類,注入了很多的相關配置的bean,包括feignRetryer、FeignLoggerFactory、FormattingConversionService等,其中還包括了Decoder、Encoder、Contract,如果這三個bean在沒有注入的情況下,會自動注入預設的配置。

  • Decoder feignDecoder: ResponseEntityDecoder(這是對SpringDecoder的封裝)
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Slf4jLogger
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: HystrixFeign.Builder
@Configuration
public class FeignClientsConfiguration {

...//省略程式碼

@Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

...//省略程式碼
}

重寫配置:

你可以重寫FeignClientsConfiguration中的bean,從而達到自定義配置的目的,比如FeignClientsConfiguration的預設重試次數為Retryer.NEVER_RETRY,即不重試,那麼希望做到重寫,寫個配置檔案,注入feignRetryer的bean,程式碼如下:

@Configuration
public class FeignClientConfig{

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, SECONDS.toMillis(1), 5);
    }

}

在上述程式碼更改了該FeignClient的重試次數,重試間隔為100ms,最大重試時間為1s,重試次數為5次。

在這種情況下,客戶端由FeignClientsConfiguration中的元件與FeignClientConfig中的任何元件組成(後者將覆蓋前者)。

需要注意的是: FeignClientConfig類不能包含在主應用程式上下文的@ComponentScan中,否則該配置會被所有的@FeignClient共享。Ribbon的自定義配置中也需要注意這個問題。可以通過將其放置在任何@ComponentScan或@SpringBootApplication的單獨的不重疊的包中,或者可以在@ComponentScan中明確排除。

2.6 .Feign 相關配置

Feign之中最為核心的作用就是將 Rest 服務的資訊轉換為介面,但是在實際使用之中也需要 考慮到一些配置情況, 例如:資料壓縮,Rest 的核心本質在於: JSON 資料傳輸( XML、文字),於是就必須思考一種情況,玩意 、文字),使用者傳送的資料很大呢? 所以這個時候可考慮修改 application.yml 配置檔案對傳輸資料進行壓縮; 配置檔案對傳輸資料進行壓縮;

feign:
   compression:
         request:
               mime-types: # 可以被壓縮的型別
#                 - text/xml
#                 - application/xml
#                 - application/json
                min-request-size: 2048 # 超過2048的位元組進行壓縮

2、 如果有需要則可以在專案之中開啟  feign的相關日誌資訊(預設不開啟) :

為每個建立的Feign客戶端建立一個記錄器。預設情況下,記錄器的名稱是用於建立Feign客戶端的介面的完整類名。Feign日誌記錄僅響應DEBUG級別
修改 application.yml配置檔案,追加日誌追蹤:

logging:
     level:
#      feign介面包所在位置
        cn.mldn.service: DEBUG

修改 FeignClientConfig ,開啟日誌的輸出:

您可以為每個客戶端配置的Logger.Level物件告訴Feign記錄多少。選擇是:

  • NONE,無記錄(DEFAULT)。

  • BASIC,只記錄請求方法和URL以及響應狀態程式碼和執行時間。

  • HEADERS,記錄基本資訊以及請求和響應標頭。

  • FULL,記錄請求和響應的標頭檔案,正文和後設資料。

例如,以下將Logger.Level設定為FULL

 
@Configuration
public class FeignClientConfig {
    @Bean
    public Logger.Level getFeignLoggerLevel() {
        return feign.Logger.Level.FULL ;
    }
   
}

 

3. Feign的Encoder、Decoder和ErrorDecoder

Feign將方法簽名中方法引數物件序列化為請求引數放到HTTP請求中的過程,是由編碼器(Encoder)完成的。同理,將HTTP響應資料反序列化為java物件是由解碼器(Decoder)完成的。

預設情況下,Feign會將標有@RequestParam註解的引數轉換成字串新增到URL中,將沒有註解的引數通過Jackson轉換成json放到請求體中。注意,如果在@RequetMapping中的method將請求方式指定為POST,那麼所有未標註解的引數將會被忽略,例如:

@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);

此時因為宣告的是GET請求沒有請求體,所以obj引數就會被忽略。

在Spring Cloud環境下,Feign的Encoder*只會用來編碼沒有新增註解的引數*。如果你自定義了Encoder, 那麼只有在編碼obj引數時才會呼叫你的Encoder。對於Decoder, 預設會委託給SpringMVC中的MappingJackson2HttpMessageConverter類進行解碼。只有當狀態碼不在200 ~ 300之間時ErrorDecoder才會被呼叫。ErrorDecoder的作用是可以根據HTTP響應資訊返回一個異常,該異常可以在呼叫Feign介面的地方被捕獲到。我們目前就通過ErrorDecoder來使Feign介面丟擲業務異常以供呼叫者處理。

Feign的HTTP Client

Feign在預設情況下使用的是JDK原生的URLConnection傳送HTTP請求,沒有連線池,但是對每個地址會保持一個長連線,即利用HTTP的persistence connection 。我們可以用Apache的HTTP Client替換Feign原始的http client, 從而獲取連線池、超時時間等與效能息息相關的控制能力。Spring Cloud從Brixtion.SR5版本開始支援這種替換,首先在專案中宣告Apache HTTP Client和feign-httpclient依賴:

<!-- 使用Apache HttpClient替換Feign原生httpclient -->
<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>${feign-httpclient}</version>
</dependency>

然後在application.properties中新增:

feign.httpclient.enabled=true

總結

通過Feign, 我們能把HTTP遠端呼叫對開發者完全透明,得到與呼叫本地方法一致的編碼體驗。這一點與阿里Dubbo中暴露遠端服務的方式類似,區別在於Dubbo是基於私有二進位制協議,而Feign本質上還是個HTTP客戶端。如果是在用Spring Cloud Netflix搭建微服務,那麼Feign無疑是最佳選擇。

參考:

http://blog.csdn.net/neosmith/article/details/52449921

https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-feign

 

相關文章