4.6 Declarative REST Client: Feign
REST客户端:Feign
Feign是一个Web服务的客户端框架。它让Web服务的客户端开发变得更容易。 只需要使用Feign创建一个接口加上一个注解就行了。Feign和JAX-RS提供一整套插件化的注解配置方式。在Feign中还可以使用插件化的编码器和解码器。
Spring Cloud 还提供一个
HttpMessageConverters
,这样当使用Spring Web环境时,就可以支持Spring MVC。 在使用Feign时,Spring Cloud还可以整合Ribbon和Eureka,为http客户端提供负载均衡的能力。
4.6.1 How to Include Feign 如何引入Feign
在工程中引入Feign只需要引入依赖就行:group :
org.springframework.cloud
artifact id :spring-cloud-starter-feign
。详情见Spring Cloud相关文档
例如:
@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负载均衡器。 还可以配置url
属性来指定URL。 在Spring容器中会使用全限定名作为这个Bean的名称。同样,可以通过name
属性进行自定义。 在上例中,通过@Qualifier("storesFeignClient")
就可以引用到这个Bean。 如果需要改变默认引用名称,可以在@FeignClient
中配置qualifier
属性。
Ribbon客户端将会查找"stores"服务的物理地址。 如果是一个Eureka客户端应用,那么将会自动注册到Eureka中。 如果不想使用Eureka,可以简单的配置一个服务列表,手动指定服务。
4.6.2 Overriding Feign Defaults 覆盖Feign默认配置
Spring Cloud 的 Feign有一个核心概念,那就是客户端命名。每一个Feign客户端都是集群中一组
@FeignClient
标记的可用服务区域中的一部分。
Spring Cloud会为每一个命名的Feign客户端中的
FeignClientsConfiguration
创建一个新的ApplicationContext
。 这其中会包括一个feign.Decoder
,一个feign.Encoder
,以及一个feign.Contract
。
Spring Cloud运行开发者通过
@FeignClient
对Feign客户端进行完全的控制。例如:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这个例子中,客户端使用一个自定义的
FooConfiguration
整合到FeignClientsConfiguration
的配置过程。
警告:
需要确保
FooConfiguration
不会被@Configuration
或者@ComponentScan
扫描到,否则,就会被所有@FeignClient
共享了(自动注入)。如果使用@ComponentScan
或者@SpringBootApplication
时,也需要避免FooConfiguration
被自动扫描。
注意:
serviceId
属性已经丢弃,不建议使用,改用name
属性进行配置。
警告:
以前使用
url
属性时,不需要name
。现在不行了,name
必须指定。
在
name
和url
属性时,可以使用占位符进行配置。例如:
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix 默认会创建下列Bean
类型 | Bean名称 | 类名 | 备注 |
---|---|---|---|
Decoder | feignDecoder | ResponseEntityDecoder | 包装了SpringDecoder |
Encoder | feignEncoder | SpringEncoder | |
Logger | feignLogger | Slf4jLogger | |
Contract | feignContract | SpringMvcContract | |
Feign.Builder | feignBuilder | HystrixFeign.Builder | |
Client | feignClient | LoadBalancerFeignClient | 如果启用Ribbon则是LoadBalancerFeignClient ,否则使用Feign默认客户端 |
还可以通过
feign.okhttp.enabled
或者feign.httpclient.enabled
来开启OkHttpClient和ApacheHttpClient,当然前提是classpath中必须有这些相关依赖。
默认情况下,Spring Cloud Netflix不会为Feign提供下列的Bean,但是仍然可以在spring上下文中通过类型找到它们来创建Feign客户端。
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
可以在
@FeignClient
的配置类中,自行创建这些Bean来订制Feign客户端。例如:
@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
。
默认配置可以通过
@EnableFeignClients
的defaultConfiguration
属性来制定。这些配置,会被应用到所有的Feign客户端上。
4.6.3 Creating Feign Clients Manually 手动创建Feign客户端
在某些场景中,通过上述方法,还是不能得到一个想要的Feign客户端。 那这个时候,就需要自己通过Feign Builder API来手动创建客户端。 下面这个例子就展现了为同一个接口创建两个不同配置的客户端。
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(
ResponseEntityDecoder decoder, SpringEncoder 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.class
是Spring Cloud Netflix提供的默认配置类。
注意: PROD-SVC
是服务的客户端请求时标记的名称。
4.6.4 Feign Hystrix Support :Feign断路器
如果在classpath中加入了Hystrix,那默认Feign就会为所有方法都包装上一个断路器。也会返回一个
com.netflix.hystrix.HystrixCommand
。这就会导致可以使用正则匹配(如:调用.toObservable()
or.observe()
)或者异步请求(如:调用.queue()
)。
如果不想在Feign使用Hystrix,可以配置:
feign.hystrix.enabled=false
。
如果想让每个客户端都不使用Hystrix,可以在
prototype
作用域中创建一个普通的Feign.Builder
实现。例如:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
4.6.5 Feign Hystrix Fallbacks : Feign降级
Hystrix中的降级概念:当链路中发生错误是,执行一个默认的代码逻辑。 可以在
@FeignClient
中配置fallback
属性,来开启Feign降级处理。例如:
@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的降级操作与Hystrix降级操作对比有一个限制。降级操作,对于方法的返回类型目前还不支持com.netflix.hystrix.HystrixCommand
和rx.Observable
。
4.6.6 Feign Inheritance Support 继承
Feign只支持接口层的单继承。可以将一些通用操作抽象成一个基础接口。例如:
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版本中也不支持这种方式(共用接口)。
4.6.7 Feign request/response compression 数据压缩
可以考虑对Feign的request和response开启GZIP压缩处理。可以在配置文件中通过如下配置开启:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
在web服务中进行更详细的配置:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
上例中,指定了Media type和压缩的最小大小进行了指定。
4.6.8 Feign logging 日志
对于创建的每个Feign客户端都会创建一个Logger。默认情况下,使用客户端全类名作为日志名,Feign日志只能设置为
DEBUG
级别。
application.yml
logging.level.project.user.UserClient: DEBUG
可以为每个客户端配置一个
Logger.Level
对象,来告诉Feign如何输出日志。可以选择以下级别:
NONE
,无日志(默认)BASIC
,仅输出请求的方法、URL、response status code 以及执行时间HEADERS
,带上request和response的header信息FULL
,包括requset和response的header,body以及元数据
例如:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
4.7 External Configuration: Archaius 使用Archaius进行外部扩展配置
Archaius是一个Netflix客户端配置的类库。这个类库可以被用于所有的Netflix OSS组件的配置。Archaius是在Apache Commons Configuration的基础山进行扩展出来的。Archaius允许客户端轮询自动发现配置的改动变化。Archaius通过
Dynamic<Type>Property
类来处理各种配置属性。
Archaius Example
class ArchaiusTest {
DynamicStringProperty myprop = DynamicPropertyFactory
.getInstance()
.getStringProperty("my.prop");
void doSomething() {
OtherClass.someMethod(myprop.get());
}
}
Archaius有自己的配置文件集以及预先加载机制。Spring应用通常不会直接使用Archaius,而是通过Netflix原生的配置工具进行使用。Spring Cloud提供一个Spring Environment来对Archaius属性进行桥接使用。这样就可以让Spring Boot应用能像使用普通配置属性一样使用Archaius。