- [Spring Cloud OpenFeign](#spring-cloud-openfeign) - [Feign](#feign) - [引入Feign](#引入feign) - [@FeignClient注解的属性解析模式](#feignclient注解的属性解析模式) - [覆盖Feign的默认属性](#覆盖feign的默认属性) - [重试](#重试) - [通过Configuration类或配置文件来修改Feign Client的默认行为](#通过configuration类或配置文件来修改feign-client的默认行为) - [超时处理](#超时处理) - [Feign Caching](#feign-caching) # Spring Cloud OpenFeign ## Feign Feign是一个声明式的web service client,其能够让编写web service client的过程更加简单。在使用Feign时,只需要创建一个接口并且为其添加注解。 在使用Feign时,Spring Cloud集成了Eureka、Spring Cloud CircuitBreaker和Spring Cloud LoadBalancer来向使用者提供一个负载均衡的http client。 ### 引入Feign 如果要在项目中引入Feign,可以添加如下启动器依赖: ```xml org.springframework.cloud spring-cloud-starter-openfeign ``` 在使用Feign时,需要为启动类添加`@EnableFeignClients`注解,示例如下所示: ```java @SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 使用Feign时,定义接口并添加注解的示例如下所示: ```java @FeignClient("stores") public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List getStores(); @RequestMapping(method = RequestMethod.GET, value = "/stores") Page getStores(Pageable pageable); @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store); @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}") void delete(@PathVariable Long storeId); } ``` 上述示例中,@FeignClient中指定的字符串是一个任意的client name,用于创建spring cloud loadbalancer client,对于该注解还可以指定一个`url`属性。在spring context中,该bean对象的name即是接口的全类名,bean name也可以通过@FeignClient注解中的`qualifiers`属性来替换。 上述的loadbalancer client会取发现"stores" service对应的物理地址,如果你的应用是一个Eureka client,那么loadbalancer client会从Eureka注册中心中进行解析。 #### @FeignClient注解的属性解析模式 当创建Feign client bean对象时,会解析传递给@FeignClient注解的属性值。这些值默认是立即解析的。 如果如果需要延迟这些属性值的解析时机,可以配置`spring.cloud.openfeign.lazy-attributes-resolution`的值为true。 ### 覆盖Feign的默认属性 Spring Cloud Feign的核心理念是命名客户端(named client)。每个Feign client都是组件集合一部分,组件集合协同工作,并且在需要时连接远程的server。该组件集合有一个名称,该值通过@FeignClient的value属性赋值。对于每个named client,Spring Cloud使用`FeignClientsConfiguration`来创建一个新的集合作为Application Client。 组件集合中包含有一个`feign.Decoder`,一个`feign.Encoder`和`feign.Contract`。可以通过@FeignClient注解中的`contextId`属性来覆盖集合name。 Spring Cloud允许通过声明额外的configuration来完全掌控feign client,示例如下: ```java @FeignClient(name = "stores", configuration = FooConfiguration.class) public interface StoreClient { //.. } ``` 在这种情况下,client由`FeignClientsConfiguration`中已经存在的组件和`FooConfiguration`中的组件共同组成,并且后者会覆盖前者。 > FooConfiguration类在声明时不要为其添加@Configuration注解,否则其会成为feign.Decoder, feign.Encoder, feign.Contract的默认来源。 对于`name`和`url`属性,赋值支持占位符: ```java @FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { //.. } ``` #### 重试 一个`Retryer.NEVER_RETRY`的bean对象会被默认创建,其会禁止重试操作。但是,其会自动重试IOException,将IOException看作暂时性地网络异常,并且在ErrorDecoder中抛出RetryableException。 在@FeignClient注解`configuration`属性指定的配置类中创建Bean对象可以允许覆盖默认的bean对象,示例如下: ```java @Configuration public class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } } ``` #### 通过Configuration类或配置文件来修改Feign Client的默认行为 @FeignClient也可以通过配置文件来进行配置: ```yml spring: cloud: openfeign: client: config: {feignName}: url: http://remote-service.com connectTimeout: 5000 readTimeout: 5000 loggerLevel: full errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer defaultQueryParameters: query: queryValue defaultRequestHeaders: header: headerValue requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor responseInterceptor: com.example.BazResponseInterceptor dismiss404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract capabilities: - com.example.FooCapability - com.example.BarCapability queryMapEncoder: com.example.SimpleQueryMapEncoder micrometer.enabled: false ``` feignName在上述配置文件中代表@FeignClient注解中的value值。 > 若feignName的值为default,可以将配置应用于所有的feign client。 可以在`@EnableFeignClients`注解中指定defaultConfiguration属性,其可以指定一个自定义的默认配置类,覆盖的行为会作用于所有的Feign Client。 可以使用`spring.cloud.openfeign.client.config.feignName.defaultQueryParameters`和`spring.cloud.openfeign.client.config.feignName.defaultRequestHeaders`来指定client每次请求都会发送的请求参数和请求header。 如果在创建Configuration类的同时也指定了配置文件,那么配置文件的优先级更高,配置文件的值会覆盖Configuration类中的值。 如果想要多个feign client拥有相同的name和url,以使它们指向同一个server,但不同feign client拥有不同的配置。可以为它们指定不同的`contextId`。 ```java @FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) public interface FooClient { //.. } @FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) public interface BarClient { //.. } ``` ### 超时处理 可以为named client或所有client指定超时处理。OpenFeign使用两个timeout参数: - connectTimeout:该属性避免server长时间处理造成的调用方阻塞 - readTimeout:该属性从连接建立时开始应用,并且当返回响应过慢时被触发(该参数衡量的是从建立连接到返回响应的过程) ### Feign Caching 当使用@EnableCaching之后,一个`CachingCapability`的bean对象将会被配置,Feign client将会识别接口上的`@Cache*`注解。 ```java public interface DemoClient { @GetMapping("/demo/{filterParam}") @Cacheable(cacheNames = "demo-cache", key = "#keyParam") String demoEndpoint(String keyParam, @PathVariable String filterParam); } ```