OpenFeign 2.1.4 Release Doc
这个项目通过自动装配和其他Spring程序模型的习惯用法,把给SpringBoot应用提供的OpenFeign的综合包绑定到Spring的项目环境中。
1 说明
Feign是一个声明式的web服务客户端。它使编写web服务客户端变得更加简单。创建一个接口和注解就能使用Feign。它有可插入的注解支持,包括Feign的注解和JAXRS注解。Feign也支持可插入的编码器和解码器。SpringCloud为Spring MVC 注解和在Spring Web中使用相同的HttpMessageConverters,提供了支持。SpringCloud合并了Ribbon和Eureka,而且在使用Feign时,Spring Cloud LoadBalancer 提供了一个负载均衡的http客户端。
1.1 如何添加Feign
要在你的项目中加入Feign,使用group为org.springframework.cloud,artifact id为spring-cloud-starter-openfeign的starter。
使用Feign时要在启动类中加入注解:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Feign的接口声明:
@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支持的详细信息,请参阅下面)或Spring Cloud LoadBalancer。还可以使用URL属性(绝对值或主机名)指定URL。应用程序上下文中bean的名称是接口的完全限定名。要指定自己的别名值,可以使用@FeignClient注释的限定符值。
上面的负载均衡器客户机希望发现“stores”服务的物理地址。如果您的应用程序是一个Eureka客户端,那么它将在Eureka服务注册表中解析服务。如果您不想使用Eureka,您可以简单地在外部配置中配置一个服务器列表(参见上面的示例)。
注意:为了保持向后兼容性,被用作默认的负载平衡器实现。但是,Spring Cloud Netflix Ribbon现在处于维护模式,所以我们建议使用Spring Cloud LoadBalancer来代替。为此,设置spring.cloud.loadbalker .ribbon.enabled为false。
1.2 重写Feign默认实现
SpringCloud的Feign支持的核心概念是客户端的名称。每一个Feign客户端,都是根据需要联系远程服务器一起工作的整体组件的一部分,你要用@FeignClient注解为这个整体指定一个名字。SpringCloud会按需为每一个使用FeignClientsConfiguration的客户端创建一个新的整体作为一个ApplicationContext。ApplicationContext包括了一个 feign.Decoder
,一个 feign.Encoder
,一个 feign.Contract
。你可以使用@FeignClient注解的name属性改变这个整体的名字。
SpringCloud能让你通过额外的声明配置,来完全控制feign客户端。例如:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这种情况下,客户端是由FeignClientsConfiguration中已经存在的组件和FooConfiguration中的任何组件(其中后者将覆盖前者)组成的。
注意:FooConfiguration不需要使用@Configuration进行注释。但是,如果用了,那么要注意将它排除在任何可能包含@ComponentScan配置之外,因为它将成为feign中feign.Decoder
,feign.Encoder
, feign.Contract
等的默认源。可以通过将它放在任何@ComponentScan或@SpringBootApplication中单独的、不重叠的包中来避免这种情况,也可以在@ComponentScan中显式地排除它。
以前,使用url属性时不需要name属性。现在需要使用name。属性支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix 为Feign提供了以下beans(BeanType 的bean名称是 class名):
Decoder
,feignDecoder:ResponseEntityDecoder
(包装了一个SpringDecoder)Encoder
, feignEncoder:SpringEncoder
Logger
,feignLogger:Slf4jLogger
Contract
,feignContract:SpringMvcContract
Feign.Builder
,feignBuilder:HystrixFeign.Builder
Client
,feignClient:如果Ribbon在类路径中,并且启用了它,那么它就是LoadBalancerFeignClient,否则,如果Spring Cloud LoadBalancer在类路径中,那么就使用FeignBlockingLoadBalancerClient。如果它们都不在类路径中,则使用默认的feign客户端。
注意:spring-cloud-starter-openfeign
包括了spring-cloud-starter-netflix-ribbon
和spring-cloud-starter-loadbalancer
。
OkHttpClient 和 ApacheHttpClient 的Feign客户端可以通过各自的配置项feign.okhttp.enabled
或 feign.httpclient.enabled
设为true
,并将它们放在类路劲中,来使用。你可以定制Http客户端,当你使用Apache时使用org.apache.http.impl.client.CloseableHttpClient,当你使用OK HTTP 时,使用okhttp3.OkHttpClient。
Spring Cloud Netflix 没有提供下列feign默认的bean,但还是从应用程序上下文中查找这些bean来创建Feign客户端:
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
QueryMapEncoder
创建一个那些类型之一的bean,并用@FeignClient
替代配置,允许你重写任意一个已经声明的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
中相似的方式改动默认配置。其中的不同之处,是用改动@EnableFeignClients
属性的方式配置,那么改动的配置将应用于所有Feign客户端。
如果你更喜欢用配置文件来配置所有的@FeignClient
,你可以创建一个名为default
的feign配置(application.yml):
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
如果我们同时使用@Configuration
和配置文件,会优先使用配置文件的内容,系统将覆盖@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客户端,那么它们需要指向相同的服务,但每一个都要有不同的个性化配置,这时候我们要使用@FeignClient
的contextId
属性,来避免这些配置bean的名称冲突。
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}
1.3 手动创建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, "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提供的默认配置。PROD-SVC
是客户端服务的名称,将标记出请求去到哪里。Feign的Contract
对象定义接口上有效的注解和值。自动注入Contract
的bean提供了SpringMVC 支持,以取代默认的Feign本地注解。
1.4 Feign Hystrix 支持
如果Hystrix在类路径中,且feign.hystrix.enabled=true
,Feign会将所有方法用断路器包裹起来。还可以返回com.netflix.hystrix.HystrixCommand
。这允许您使用响应模式(通过调用. toobservable()
或.observe()
或异步使用(通过调用.queue()
)。
要在每个客户端基础上禁用Hystrix支持,请创建一个具有“prototype”scope的Feign.Builder,例如:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
注意:在Spring Cloud Dalston发布之前,如果Hystrix在类路径上,那么在默认情况下,Feign会将所有方法封装在一个断路器中。在Spring Cloud Dalston中更改了此默认行为,以支持选择进入方法。
1.5 Feign Hystrix Fallbacks
Hystrix支持fallback的概念:当电路断开或出现错误时执行的默认代码。要为给定的@FeignClient
启用fallback,请将fallback
属性设置为实现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");
}
}
如果需要访问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中fallback的实现和Hystrix fallback的工作方式有一定的局限性。当前fallbakcs并不支持返回值为com.netflix.hystrix.HystrixCommand
和rx.Observable
的方法。
1.6 Feign 和 @Primary
当开启Hystrix fallbacks使用Feign,有多个bean在ApplicationContext
中是相同类型的。这会造成@Autowired
不起作用,因为这里不只找到一个bean对象,或者没有一个被标记为主要的。Spring Cloud Netflix标记了所有的Feign接口为@Primary
来解决这个问题,所以Spring Framework将会知道那个bean应该注入。有时候,这可能不合理。可以在@FeignClient
上设置primary=false
属性关掉这个习惯。
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
1.7 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 {
}
UserController.java
package project.user;
@FeignClient("users")
public interface UserClient extends UserService {
}
注意:通常不建议在服务器和客户端之间共享接口。它引入了紧密耦合,而且在当前的形式下,它实际上也不能与Spring MVC一起工作(方法参数映射不是继承的)。
1.8 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
这些属性允许您选择压缩媒体类型和最小请求阈值长度。
对于除OkHttpClient外的http客户端,可以启用默认的gzip解码器对gzip响应进行UTF-8编码解码:
feign.compression.response.enabled=true
feign.compression.response.useGzipDecoder=true
1.9 Feign 日志
将为创建的每个Feign客户端创建一个日志程序。默认情况下,日志程序的名称是用于创建Feign客户端的接口的完整类名。Feign日志只对DEBUG级别有响应。
application.yml
logging.level.project.user.UserClient: DEBUG
你可能要为每个客户端配置Logger.Level
对象,来告诉Feign要记录多少日志。选项有:
NONE
,不记录日志,默认。BASIC
,只记录请求方法和URL以及响应状态代码和执行时间。HEADERS
,记录基本信息以及请求和响应标头。FULL
,记录请求和响应的标题、正文和元数据。
下面就是将Logger.Level
设置为FULL
的例子:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
1.10 Feign @QueryMap 支持
OpenFeign的@QueryMap
注解提供了将POJO作为GET请求参数的支持。不幸的是,默认的OpenFeign QueryMap注释与Spring不兼容,因为它缺少value属性。
Spring Cloud OpenFeign提供了一个等价的@SpringQueryMap注释,用于将POJO或Map参数注释为查询参数映射。
// Params.java
public class Params {
private String param1;
private String param2;
// [Getters and setters omitted for brevity]
}
下面的feign客户端通过使用@SpringQueryMap注释来使用Params类:
@FeignClient("demo")
public interface DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
如果您需要对生成的查询参数映射有更多的控制,您可以实现一个定制的QueryMapEncoder bean。
1.11 HATEOAS的支持
Spring提供了一些api来创建遵循HATEOAS原则、Spring HATEOAS和Spring Data REST的REST表示。