一、概述
前面的文章中提到,如果我们要调用其它应用的服务,只能够通过 RestTemplate 的方式,这在我们实际的开发中很不方便。那么有没有类似于 Dubbo 中 @Reference 这样的注解直接调用呢?这就是我们今天要讲的 Spring Cloud Feign。
Spring Cloud Feign 基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix,除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式。
Spring Cloud Feign 具备可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。同时,为了适应 Spring 的广大用户,它在 Netflix Feign 的基础上扩展了对 Spring MVC 的注解支持。
二、Feign 实战
SpringBoot 版本号:2.1.6.RELEASE
SpringCloud 版本号:Greenwich.RELEASE
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2. application.yml
server:
port: 2032
spring:
application:
name: cloud-feign-consumer
eureka:
client:
service-url:
defaultZone: http://user:password@localhost:1111/eureka/
3. FeignApplication.java
// 开启 Spring Cloud Feign 的支持功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
4. 接口定义
@FeignClient(value = "cloud-eureka-client")
public interface FeignService {
@RequestMapping("/hello")
String hello();
}
这里我们定义了一个接口(方法名和注解与服务提供方一致),并通过 @FeignClient 注解绑定到服务提供方。当调用 FeignService.hello() 的时候,Feign 会把请求包装成 "http://cloud-eureka-client/hello" 的样子,通过 RestTemplate 调用并返回结果。
对于 Spring Cloud Feign 的参数绑定,就是当调用方法需要有参数的时候,参数格式只需按照 Spring MVC 的注解即可。区别是:在定义各参数绑定时,@RequestParam、@RequestHeader 等可以指定参数名称的注解,它们的 value 千万不能少。在 SpringMVC 程序中,这些注解会根据参数名来作为默认值,但是在 Feign 中绑定参数必须通过 value 属性来指明具体的参数名。
对于在服务提供方和服务调用方都维护一份接口定义的做法着实不提倡,原因很简单,修改了一个方法,需要同时在两个地方做出改变。比较好的做法是:服务提供方暴露一个 xxx-interface 的 jar 包供服务调用方引用。这样,服务调用方,直接引用 xxx-interface 的方法,不维护接口定义,不关心实现。
5. Spring Cloud Feign 中的 Ribbon 配置
由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来定义各个服务客户端调用的参数。
# 全局配置
ribbon:
# 连接超时时间
ConnectTimeout: 500
# 调用超时时间
ReadTimeout: 2000
# 针对单个服务的 Ribbon 配置
cloud-eureka-client:
ribbon:
# 重试次数
MaxAutoRetries: 2
我们需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。
配置参数可以在 CommonClientConfigKey.java 中查询到,具体每个参数的含义,就不在这里细讲了。
6. Spring Cloud Feign 中的 Hystrix 配置
默认情况下,Spring Cloud Feign 会为将所有 Feign 客户端的方法都封装到 Hystrix 命令中进行服务保护。当然,可以在配置文件中选择开启或者关闭 Hystrix:
feign:
hystrix:
enabled: true
如果我们仅要在某个服务中关闭 Hystrix 呢?那么我们就要自定义一个配置类了:
@Configuration
public class DisableHystrixConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Feign.Builder feignBuilder() {
return new Feign.Builder();
}
}
然后再 @FeignClient 注解中引用:
@FeignClient(value = "cloud-eureka-client", configuration = DisableHystrixConfig.class)
上一篇文章 我们看到 Hystrix 中有很多的配置参数,那么在 Feign 中如何配置它们呢?
# 配置全局的超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutinMilliseconds: 5000
# 针对某个 commandKey 做配置,而 commandKey 默认取得是客户端中的方法名作为标识。所以如果存在相同方法名会共用配置。
hello:
execution:
isolation:
thread:
timeoutinMilliseconds: 5000
Hystrix 的配置参数可以在 HystrixCommandProperties.java 中找到。
7. 服务降级
在 Hystrix 中服务降级我们通过 fallbackMethod 来实现,那么 Feign 中没法直接使用 @HystrixCommand 注解,要怎么配置服务降级呢?首先。我们要实现一个需要降级的接口,并提供降级实现:
@Component
public class FeignServiceFallback implements FeignService {
@Override
public String hello() {
return "error";
}
}
@FeignClient(value = "cloud-eureka-client", configuration = DisableHystrixConfig.class, fallback = FeignServiceFallback.class)
8. 日志配置
Spring Cloud Feign 在构建被 @FeignClient 注解修饰的服务客户端时,会为每一个客户端都创建一个 Logger.Level 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
Logger.Level 有四种级别:
- NONE: 不记录任何信息。
- BASIC: 仅记录请求方法、URL以及响应状态码和执行时间。
- HEADERS: 除了记录 BASIC 级别的信息之外,还会记录请求和响应的头信息。
- FULL: 记录所有请求与响应的明细,包括头信息、请求体、元数据等。
:该配置项只有 Spring 的日志级别为 Debug 时才生效。
9. 其他配置
Spring Cloud Feign 支持对请求与响应进行 GZIP 压缩,以减少通信过程中的性能损耗。
feign:
compression:
# 开启请求和响应的压缩功能
request:
enabled: true
# 超过 2M 才开始压缩
min-request-size: 2048
# 压缩类型
mime-types: {"text/xml", "application/xml", "application/json"}
response:
enabled: true