Spring Cloud 參考文檔(聲明式REST客戶端:Feign)

聲明式REST客戶端:Feign

Feign是一個聲明式的Web服務客戶端,它使編寫Web服務客戶端變得更容易,要使用Feign,請建立一個接口並對其進行註解,它具備可插拔的註解支持,包括Feign註解和JAX-RS註解,Feign還支持可插拔編碼器和解碼器。Spring Cloud增長了對Spring MVC註解的支持,並使用了Spring Web中默認使用的相同HttpMessageConverters,Spring Cloud集成了Ribbon和Eureka,在使用Feign時提供負載均衡的http客戶端。java

如何包含Feign

要在項目中包含Feign,請使用包含組名爲org.springframework.cloud和工件名爲spring-cloud-starter-openfeign的啓動器。git

spring boot應用示例github

@SpringBootApplication
@EnableFeignClients
public class Application {

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

}

StoreClient.javaspring

@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註解中,String值(上面的「stores」)是一個任意客戶端名稱,用於建立Ribbon負載均衡器,你還可使用url屬性指定URL(絕對值或僅指定主機名),應用程序上下文中bean的名稱是接口的徹底限定名稱,要指定本身的別名值,可使用@FeignClient註解的qualifier值。json

上面的Ribbon客戶端將想要發現「stores」服務的物理地址,若是你的應用程序是Eureka客戶端,那麼它將解析Eureka服務註冊表中的服務,若是你不想使用Eureka,只需在外部配置中配置服務器列表。api

覆蓋Feign默認值

Spring Cloud的Feign支持的核心概念是命名客戶端,每一個feign客戶端都是一個組件集成的一部分,這些組件協同工做以按需聯繫遠程服務器,而且集成有一個名稱,做爲使用@FeignClient註解的應用程序開發人員可使用這個名稱。Spring Cloud使用FeignClientsConfiguration按需爲每一個命名客戶端建立一個新的集成做爲ApplicationContext,這包含(除其餘外)feign.Decoderfeign.Encoderfeign.Contract,可使用@FeignClient註解的contextId屬性覆蓋該集成的名稱。服務器

Spring Cloud容許你經過使用@FeignClient聲明其餘配置(在FeignClientsConfiguration之上)來徹底控制feign客戶端,例如:app

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

在這種狀況下,客戶端由FeignClientsConfiguration中已有的組件以及FooConfiguration中的任何組件組成(後者將覆蓋前者)。負載均衡

FooConfiguration不須要使用 @Configuration註解,可是,若是是,則注意將其從任何包含此配置的 @ComponentScan中排除,由於它將成爲 feign.Decoderfeign.Encoderfeign.Contract等的默認源。這能夠經過將其放在任何 @ComponentScan@SpringBootApplication的單獨的非重疊包中來避免,也能夠在 @ComponentScan中明確排除。
如今不推薦使用 serviceId屬性,而是使用 name屬性。
使用 @FeignClient註解的 contextId屬性除了更改 ApplicationContext集成的名稱,它將覆蓋客戶端名稱的別名,它將用做爲該客戶端建立的配置bean名稱的一部分。
之前,使用 url屬性不須要 name屬性,如今須要使用 name

nameurl屬性支持佔位符。異步

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

Spring Cloud Netflix默認爲feign(BeanType beanName:ClassName)提供如下bean:

  • Decoder feignDecoder:ResponseEntityDecoder(包裝SpringDecoder
  • Encoder feignEncoder:SpringEncoder
  • Logger feignLogger:Slf4jLogger
  • Contract feignContract:SpringMvcContract
  • Feign.Builder feignBuilder:HystrixFeign.Builder
  • Client feignClient:若是啓用了Ribbon,則它是LoadBalancerFeignClient,不然使用默認的feign客戶端。

能夠經過將feign.okhttp.enabledfeign.httpclient.enabled分別設置爲true,並將它們放在類路徑上來使用OkHttpClientApacheHttpClient feign客戶端,你能夠經過在使用Apache時提供ClosableHttpClient或在使用OK HTTP時提供OkHttpClient的bean來定製使用的HTTP客戶端。

Spring Cloud Netflix默認狀況下不爲feign提供如下bean,但仍然從應用程序上下文中查找這些類型的bean以建立feign客戶端:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory

建立其中一種類型的bean並將其放在@FeignClient配置中(如上面的FooConfiguration)容許你覆蓋所描述的每一個bean,例如:

@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替換SpringMvcContract,並將RequestInterceptor添加到RequestInterceptor的集合中。

@FeignClient也可使用配置屬性進行配置。

application.yml

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

能夠以與上述相似的方式在@EnableFeignClients屬性defaultConfiguration中指定默認配置,不一樣之處在於此配置將適用於全部feign客戶端。

若是你更喜歡使用配置屬性來配置全部@FeignClient,則可使用default feign名稱建立配置屬性。

application.yml

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

若是咱們同時建立@Configuration bean和配置屬性,配置屬性將獲勝,它將覆蓋@Configuration值,可是,若是要將優先級更改成@Configuration,則能夠將feign.client.default-to-properties更改成false

若是須要在 RequestInterceptor中使用 ThreadLocal綁定變量,則須要將Hystrix的線程隔離策略設置爲「SEMAPHORE」或在Feign中禁用Hystrix。

application.yml

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

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

若是咱們想要建立具備相同名稱或URL的多個feign客戶端,以便它們指向同一服務器但每一個都具備不一樣的自定義配置,那麼咱們必須使用@FeignClientcontextId屬性,以免這些配置bean的名稱衝突。

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
    //..
}
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
    //..
}

手動建立Feign客戶端

在某些狀況下,可能須要以使用上述方法沒法實現的方式自定義Feign客戶端,在這種狀況下,你可使用Feign Builder API建立客戶端。下面是一個示例,它建立兩個具備相同接口的Feign客戶端,但使用單獨的請求攔截器配置每一個客戶端。

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

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

        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "http://PROD-SVC");
    }
}
在上面的示例中, FeignClientsConfiguration.class是Spring Cloud Netflix提供的默認配置。
PROD-SVC是客戶端將向其發出請求的服務的名稱。
Feign Contract對象定義了哪些註解和值在接口上是有效的,自動裝配的 Contract bean提供對SpringMVC註解的支持,而不是默認的Feign原生註解。

Feign Hystrix支持

若是Hystrix位於類路徑而且feign.hystrix.enabled=true,則Feign將使用斷路器包裝全部方法,返回com.netflix.hystrix.HystrixCommand也可用,這容許你使用反應模式(經過調用.toObservable().observe()或異步使用(經過調用.queue())。

要在每一個客戶端的基礎上禁用Hystrix支持,請建立一個帶有「prototype」範圍的vanilla F​​eign.Builder,例如:

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

在Spring Cloud Dalston發佈以前,若是Hystrix在類路徑上,Feign會默認將全部方法包裝在斷路器中,Spring Cloud Dalston中更改了此默認行爲,轉而採用了選擇加入方法。

Feign Hystrix Fallback

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 HystrixClient() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}
在Feign中實現回退以及Hystrix回退如何工做都有必定的限制,返回 com.netflix.hystrix.HystrixCommandrx.Observable的方法目前不支持回退。

Feign和@Primary

當與Hystrix回退一塊兒使用Feign時,ApplicationContext中有相同類型的多個bean,這將致使@Autowired沒法工做,由於沒有一個明確的bean或一個標記爲primary的bean。爲了解決這個問題,Spring Cloud Netflix將全部Feign實例標記爲@Primary,所以Spring Framework將知道要注入哪一個bean,在某些狀況下,這可能並不理想,要關閉此行爲,請將@FeignClientprimary屬性設置爲false

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

Feign繼承支持

Feign經過單繼承接口支持樣板api,這容許將通用操做分組爲方便的基本接口。

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(方法參數映射不會被繼承)。

Feign請求/響應壓縮

你能夠考慮爲你的Feign請求啓用請求或響應GZIP壓縮,你能夠經過啓用如下屬性之一來執行此操做:

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

Feign請求壓縮爲你提供相似於你爲Web服務器設置的設置:

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

經過這些屬性,你能夠選擇壓縮介質類型和最小請求閾值長度。

Feign記錄日誌

爲每一個建立的Feign客戶端建立一個記錄器,默認狀況下,記錄器的名稱是用於建立Feign客戶端的接口的完整類名,Feign日誌記錄僅響應DEBUG級別。

application.yml

logging.level.project.user.UserClient: DEBUG

你能夠爲每一個客戶端配置Logger.Level對象,告訴Feign要記錄多少,選擇是:

  • NONE,沒有記錄(DEFAULT)。
  • BASIC,僅記錄請求方法和URL以及響應狀態代碼和執行時間。
  • HEADERS,記錄基本信息以及請求和響應headers。
  • FULL,記錄請求和響應的headers、body和元數據。

例如,如下內容將Logger.Level設置爲FULL

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

Feign @QueryMap支持

OpenFeign @QueryMap註解爲POJO提供了支持,可用做GET參數映射,不幸的是,默認的OpenFeign QueryMap註解與Spring不兼容,由於它缺乏value屬性。

Spring Cloud OpenFeign提供等效的@SpringQueryMap註解,用於將POJO或Map參數註解爲查詢參數映射。

例如,Params類定義參數param1param2

// Params.java
public class Params {
    private String param1;
    private String param2;

    // [Getters and setters omitted for brevity]
}

如下feign客戶端使用@SpringQueryMap註解使用Params類:

@FeignClient("demo")
public class DemoTemplate {

    @GetMapping(path = "/demo")
    String demoEndpoint(@SpringQueryMap Params params);
}
相關文章
相關標籤/搜索