1. 程式人生 > >《Spring Cloud Netflix官方文件》7.宣告式 REST 客戶端: Feign

《Spring Cloud Netflix官方文件》7.宣告式 REST 客戶端: Feign

原文連結

Feign 是一個宣告式的web服務客戶端。它使得編寫web服務客戶端更簡單,建立一個介面並加上註解就能使用Feign了,它還支援JAX-RS型別的註解,可插入式的編碼和解碼,Spring cloud 為他加入了spring mvc的註解支援,以及在spring web開發過程中預設使用同樣的 HttpMessageConverters 。Spring Cloud整合了Ribbon和Eureka為使用feign的過程中提供了一個負載均衡的http客戶端。

7.1 開始使用 Feign

在你的專案中使用Feign可以使用 group為 org.springframework.cloud

,artifact id 為spring-cloud-starter-feign的 starter 來引入依賴。通過檢視 Spring Cloud Project page 來獲取更多關於當前版本Spring cloud的資訊。

Spring boot專案示例:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

StoreClient.java

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

@FeignClient註解中的stores字串是一個任意的客戶端(微服務)名字,它是用來建立一個Ribbon的負載均衡客戶端( 關於Ribbon的支援),你同樣可以指定一個url屬性(絕對值或者主機名)。這個bean在應用上下文中的名字為介面的全限定名,你也可以使用 @FeignClient 註解中的 qualifier 屬性給bean指定一個別名

上面的 Ribbon 客戶端(Feign底層使用Ribbon)將會去找尋 “stores” 微服務的實體地址,如果你的應用是一個Eureka的客戶端Feign會去Eureka的註冊中心去找,如果你不想使用Eureka,你可以在專案外部的配置中簡單的配置一個服務列表(看上一節的使用樣例)

7.2 重寫Feign配置

Spring Cloud對Feign的支援最核心的概念就是客戶端的命名,每個feign客戶端其實就是Feign全體元件中的一部分,在需要時,一起完成呼叫遠端微服務的工作。應用的開發者使用@FiengClient註解給這個總體部分命名,Spring cloud 通過 FeignClientsConfiguration 為每一個已命名客戶端建立一個應用上下文。這裡麵包含 一個 feign.Decoder, 一個 feign.Encoder 和一個 feign.Contract.

在 Spring Cloud 中,你可以通過 @FeignClient 註解宣告額外的配置(比 FeignClientsConfiguration 級別高)去控制feign客戶端,例如:


@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}

在上面這個示例中,feign客戶端在FooConfiguration中的配置將會覆蓋FeignClientsConfiguration中的配置。

注意:

  • FooConfiguration不需要使用@Configuration註解。如果加上了,需要將它從@ComponentScan註解中排除否則它將會作為預設的 feign.Decoder, feign.Encoder, feign.Contract 等等元件 配置來源,如果加上了@Configuration註解,你可以將它放在一個分離的,非重疊性的 @ComponentScan 註解或者@SpringBootApplication 註解掃描包中,或者在@ComponentScan中顯示的排除掉
  • serviceId屬性被刪除了,使用name屬性>
  • 以前使用url屬性的時候,name屬性是不需要的,現在需要了

也可以在nameurl屬性中使用佔位符


@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}

Spring Cloud Netfix 預設給 feign 提供下列的beans(格式“Bean型別 Bean名字: 類名”)
Decoder feignDecoder: ResponseEntityDecoder (包裝了SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contrac feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 如果開啟了Ribbon使用LoadBalancerFeignClient, 否則使用預設的 feign Client.

你也可以通過設定 feign.okhttp.enabled 或者 feign.httpclient.enabledtrue 來啟用 OkHttpClient 或者 ApacheHttpClient 用以替代預設的 HttpURLConnection. 當然,你同時也得將對應的jar放在classpath裡面.

Spring Cloud Netfix 預設沒有給feign裝配下列的beans,但是仍然可以從應用上下文中找到這些型別的bean去建立feign 客戶端:

Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection
* SetterFactory

你可以在配置類裡面建立上述型別的bean , 用@FeignClient註解裡面的configuration屬性指向配置類(像上面的 FooConfiguration 類):

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

這個配置類用 feign.Contract.Default 替換了Spring cloud netflix 預設的 SpringMvcContract,並且增加了一個請求攔截器。
可以用同樣的方法在 @EnableFeignClients 註解的 defaultConfiguration 屬性上面指定一個預設的feign配置,不同的是,這個配置將應用在所有的feign客戶端上面

注意: 如果你需要在你的 RequestInterceptor 中使用 ThreadLocal 去繫結變數,你同時還需要設定對應的hystrix執行緒隔離策略為“SEMAPHORE”或取消HystrixFeign中的使用.

application.yml:

# To disable Hystrix in Feign
feign:
  hystrix:
    enabled: false

# To set thread isolation to SEMAPHORE
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE

7.3 手動建立 Feign 客戶端

有時候你可能需要自定義你的Feign客戶端,那麼你就可以使用Feign Builder API,下面是個使用API的例子,分別建立了兩個 Feign 客戶端並配置了單獨的請求攔截器。

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(
            Decoder decoder, Encoder encoder, Client client) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "http://PROD-SVC");
        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "http://PROD-SVC");
    }
}

注意:

  • FeignClientsConfiguration 是 spring cloud netflix 預設提供的配置
  • PROD-SVC 是待請求的服務的名字

7.4 Feign Hystix 支援

如果 Hystrix 類在專案的 classpath 裡面 並且 feign.hystrix.enabled=true, Feign將用一個熔斷器(circuit breaker)包裝所有的方法,返回一個可用的 com.netflix.hystrix.HystrixCommand 類,它可以使用反應性的模式( 通過queue方法 可以呼叫 toObservable 方法或 observe方法或 asynchronous 使用)

在每個feign客戶端裡建立scope 為”prototype”型別的 Feign.Builder bean可以取消Hystrix的支援,例如:

@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

注意:

  • 在Spring Cloud Dalston版本之前,只要Feign類存在你專案的classpath裡面,Feign預設會給所有的方法都加上熔斷器(circuit breaker),這個預設的行為在Spring Cloud Dalston版本中變為可選擇的方法

7.5 Feign Hystrix 的 Fallbacks

Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given @FeignClient set the fallback attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.

Hystrix 支援回撥函式的概念: 當執行方法出錯或者斷點為開啟狀態時一段預設路徑的程式碼將會被執行,可以通過在@FeignClient註解上設定fallback屬性到對應的實現類上來開啟回撥功能,需要宣告這個實現類為spring的bean.

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

如果你想獲取造成回撥方法的原因,你可以使用@FeignClient註解的fallbackFactory屬性:

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClientWithFallBackFactory() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

注意:

  • 有一個限制存在於Feign fallbacks的實現類和 Hystrix fallbacks的工作原理。Fallbacks目前不支援返回 com.netflix.hystrix.HystrixCommandrx.Observable 類的方法

7.6 Feign 和 @Primary 註解

當使用Feign 的 Hystrix回撥方法時,有許多相同型別的beans存在於應用上下文中,這會導致@Autowired註解由於找不到具體的bean或主要的bean導致不能注入,為了解決這個問題,Spring Cloud Netflix 標記所有Feign例項bean為@Priamry型別,所以spring框架可以正確注入bean,有些情況下這並不需要,可以通過在@FeignClient註解裡設定primary屬性為off來關閉它

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
    // methods here
}

7.7 Feign 的繼承支援

Feign supports boilerplate apis via single-inheritance interfaces. This allows grouping common operations into convenient base interfaces.
Feign 通過單獨繼承介面可以支援模板apis,這可以使得將共同的操作方法都放在基礎接口裡面:

UserService.java.

public interface UserService {

    @RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
    User getUser(@PathVariable("id") long id);
}

UserResource.java.

@RestController
public class UserResource implements UserService {

}

UserClient.java.

package project.user;

@FeignClient("users")
public interface UserClient extends UserService {

}

注意:

  • 通常來說不建議在服務端和客戶端共享一個介面,它雖然是輕耦合的,但在上述例子中 Spring MVC 並不會正常工作(方法引數對映沒有被繼承)

7.8 Feign 請求/響應體 壓縮

你可以通過開啟以下的屬性開啟對Feign請求的請求體或響應體進行GZIP壓縮功能:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

Feign 請求的壓縮設定可以像設定你的web server一樣進行設定:

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

這些屬性可以允許你可選的設定壓縮檔案型別和最小的請求長度

7.9 Feign 日誌

A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the DEBUG level.

每建立一個Feign客戶端的時候也會建立一個logger,預設logger的名字為介面的全類名,Feign 僅記錄DEBUG 級別的日誌:

application.yml.

logging.level.project.user.UserClient: DEBUG

這個 Logger.Level 物件可以在每個客戶端中進行配置,不同的等級對應不同的日誌量:

  • NONE, 不記錄(預設)
  • BASIC, 僅記錄請求的方法和地址以及響應的狀態碼和執行時間
  • HEADERS, 單獨的記錄請求和響應頭的資訊
  • FULL, 記錄請求和響應的請求頭,請求體和元資料

下面演示設定Logger.LevelFULL 型別:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}