服务调用Feign之SpringCloudOpenFeign

服务调用
01 服务调用Dubbo之快速上手
02 服务调用Feign之快速上手
03 服务调用Feign之SpringCloudOpenFeign
04 服务调用Feign之配置

一 如何引入Feign

需要引入依赖group: org.springframework.cloud artifact id: spring-cloud-starter-openfeign

并且使用注解@EnableFeignClients使Feign生效

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableFeignClients
public class Application {

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

}

定义Feign接口

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(value = "stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();

@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);

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

其中@FeignClient中的属性value一般是用于创建一个Ribbon load-balancer (see below for details of Ribbon support) 或者 Spring Cloud LoadBalancer,在微服务中,该正常是服务的名称,即spring.application.name,也可以使用url属性(绝对值或仅是主机名)指定URL。

二 覆盖Feign客户端默认配置

Spring Cloud的Feign支持的中心概念是指定客户的概念。每个Feign客户端都是组件的一部分,这些组件可以一起工作以按需联系远程服务器,并且该组件的名称是您使用@FeignClient注解作为应用程序开发人员提供的。 Spring Cloud使用FeignClientsConfiguration根据需要为每个命名客户端创建一个新的集成作为ApplicationContext。其中包含一个feign.Decoder,一个feign.Encoder和一个feign.Contract。通过使用@FeignClient注解的contextId属性,可以覆盖该集合的名称。

Spring Cloud通过使用@FeignClient声明其他配置(在FeignClientsConfiguration之上),使您可以完全控制feign客户端。例:

@FeignClient中的属性可以通过占位符替换如:

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

Spring Cloud Netflix默认为feign提供了以下Bean (BeanType 的bean名称: ClassName):

  • Decoder feignDecoder: ResponseEntityDecoder (which wraps a SpringDecoder)
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Slf4jLogger
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: HystrixFeign.Builder
  • Client feignClient: 如果类包含了Ribbon并且ribbon启用了则使用LoadBalancerFeignClient, 否则如果类包含了Spring Cloud LoadBalancer则使用FeignBlockingLoadBalancerClient. 如果在类中以上两个依赖都没有, 则使用默认的feign client.

spring-cloud-starter-openfeign 支持 spring-cloud-starter-netflix-ribbonspring-cloud-starter-loadbalancer. 然后, 由于它们都是可选依赖, 需要确保要使用的那个已添加到您的项目中.

可以通过将feign.okhttp.enabledfeign.httpclient.enabled分别设置为true并将它们放在类路径中来使用OkHttpClient和ApacheHttpClient feign客户端。您可以通过使用Apache时提供org.apache.http.impl.client.CloseableHttpClient的bean或使用OK HTTP时提供okhttp3.OkHttpClient的bean来定制HTTP客户端。

默认情况下,Spring Cloud OpenFeign不会为Feign提供以下bean,但仍会从应用程序上下文中查找这些类型的bean以创建Feign客户端:

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

默认情况下,会创建一个Retryer.NEVER_RETRY类型为Retryer的bean,它将禁用重试。请注意,此重试行为不同于Feign的默认行为,在Feign默认行为中,它将自动重试IOException,将它们视为与网络临时相关的异常,以及从ErrorDecoder抛出的任何RetryableException。

@FeignClient也可以使用配置属性进行配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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中指定默认配置。不同之处在于此配置将适用于所有伪客户端。

如果您希望使用配置属性来配置所有@FeignClient,则可以使用默认伪名称创建配置属性。

1
2
3
4
5
6
7
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic

如果我们同时创建@Configuration bean和configuration属性,则配置属性将获胜。它将覆盖@Configuration值。但是,如果要将优先级更改为@Configuration,可以将feign.client.default-to-properties更改为false

注意:如果需要在RequestInterceptor中使用ThreadLocal绑定变量,则需要将Hystrix的线程隔离策略设置为SEMAPHORE或在Feign中禁用Hystrix

1
2
3
4
5
6
7
8
9
10
11
12
# To disable Hystrix in Feign
feign:
hystrix:
enabled: false

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

如果我们要创建多个具有相同名称或URL的虚拟客户端,以便它们指向同一台服务器,但每个客户端使用不同的自定义配置,则必须使用@FeignClient的contextId属性,以避免这些配置的名称冲突bean。

1
2
3
4
5
6
7
8
9
@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 Client,但为每个Feign Client配置一个单独的请求拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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, "https://PROD-SVC");

this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}

在上面的示例中,FeignClientsConfiguration.class是Spring Cloud Netflix提供的默认配置。

四 Feign Hystrix支持

如果Hystrix在类路径上并且feign.hystrix.enabled=true,则Feign将使用断路器包装所有方法。还可以返回com.netflix.hystrix.HystrixCommand。这使您可以使用反应性模式(通过调用.toObservable()或.observe()或异步使用(通过调用.queue())。

要基于每个客户端禁用Hystrix支持,请创建具有“prototype”范围的Feign.Builder,例如:

1
2
3
4
5
6
7
8
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}

在Spring Cloud Dalston发行版之前,如果Hystrix在类路径中,则Feign默认会将所有方法包装在断路器中。 Spring Cloud Dalston中更改了此默认行为,以支持选择加入方法。

五 Feign Hystrix Fallbacks

Hystrix支持fallback的概念:当它们的电路断开或出现错误时执行的默认代码路径。要为给定的@FeignClient启用fallback,请将fallback属性设置为实现回退的类名称。您还需要将实现声明为Spring bean。

1
2
3
4
5
6
7
8
9
10
11
12
@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属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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继承支持

Feign通过单继承接口支持样板API。这允许将常用操作分组为方便的基本接口。

UserService.java

1
2
3
4
5
public interface UserService {

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

UserResource.java

1
2
3
4
@RestController
public class UserResource implements UserService {

}

UserClient.java

1
2
3
4
5
6
package project.user;

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

}

通常不建议在服务器和客户端之间共享接口。它引入了紧密耦合,并且实际上也无法以当前形式与Spring MVC配合使用(方法参数映射不被继承)。

七 Feign请求/响应压缩

您可以考虑为您的Feign请求启用请求或响应GZIP压缩。您可以通过启用以下属性之一来做到这一点:

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

Feign请求压缩为您提供的设置类似于您为Web服务器设置的设置:

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

这些属性使您可以选择压缩媒体类型和最小请求阈值长度。

对于OkHttpClient以外的http客户端,可以启用默认的gzip解码器以UTF-8编码解码gzip响应:

1
2
feign.compression.response.enabled=true
feign.compression.response.useGzipDecoder=true

八 Feign日志

为每个创建的Feign客户端创建一个记录器。默认情况下,记录器的名称是用于创建Feign客户端的接口的全类名称。Feign日志仅响应DEBUG级别。

1
logging.level.project.user.UserClient: DEBUG

您可以为每个客户端配置的Logger.Level对象告诉Feign要记录多少。选择是:

  • NONE, No logging (DEFAULT).
  • BASIC, Log only the request method and URL and the response status code and execution time.
  • HEADERS, Log the basic information along with request and response headers.
  • FULL, Log the headers, body, and metadata for both requests and responses.

例如,以下将把Logger.Level设置为FULL

1
2
3
4
5
6
7
@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类定义参数param1和param2:

1
2
3
4
5
6
7
// Params.java
public class Params {
private String param1;
private String param2;

// [Getters and setters omitted for brevity]
}

以下Feign客户端通过使用@SpringQueryMap注解来使用Params类:

1
2
3
4
5
6
@FeignClient("demo")
public interface DemoTemplate {

@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}

如果需要对生成的查询参数映射进行更多控制,则可以实现自定义QueryMapEncoder Bean。