OpenFeign 简介
微服务提倡将一个原本独立的系统分成众多小型服务系统,这些小型服务系统都在独立的进程中运行,通过各个小型服务系统之间的协作来实现原本独立系统的所有业务功能。拆分后的微服务系统使用多种跨进程的方式进行通信协作,在使用 Spring Cloud 开发微服务应用时,各个服务提供者都是以 HTTP 接口的形式对外提供服务,因此在服务消费者调用服务提供者时,底层通过 HTTP Client 的方式访问。当然我们可以使用 JDK 原生的 URLConnection 、 Apache 的 HTTP Client 、 Netty 的异步 HTTP Client , Spring 的 RestTemplate 去实现服务间的调用。 但是最方便、最优雅的方式是通过 Spring Cloud Open Feign 进行服务间的调用。Spring Cloud 对Feign 进行了增强,使 Feign 支持 Spring MVC 的注解,并整合了 Ribbon 等,从而让 Feign 的使用更加方便。
什么是 OpenFeign
OpenFeign 是一个声明式 HTTP 网络请求客户端。 OpenFeign 提供了HTTP 请求的模板,通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP 请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Spring Cloud 对 Feign 进行了封装,使其支持 SpringMVC 标准注解和 HttpMessageConverters。OpenFeign 主要特性如下:
- 可插拔的注解支持, 包括 Feign 注解和 JAX-RS 注解。
- 支持可插拔的 HTTP 编码器和解码器。
- 支持 Hystrix 和它的 Fallback。
- 支持 Ribbon 的负载均衡。
- 支持 HTTP 请求和响应的压缩 。
使用 OpenFeign
在 Spring Cloud 中集成 OpenFeign 的步骤很简单,首先还是先引入依赖,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动类上添加 @EnableFeignClients 注解,如果 Feign 接口定义和启动类不在同一个路径下,还需要通过 basePackages 来指定扫描的包名。@EnableFeignClients 就像是一个开关,只有使用了该注解,OpenFeign 相关组件和配置机制才会生效。
@EnableFeignClients(basePackages = "com.yuxuan")
@EnableDiscoveryClient
@SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class,args);
}
}
使用 Feign 调用接口
/**
* 订单服务远程调用客户端
**/
@FeignClient(value = "order-service")
public interface OrderClient {
@GetMapping("/order/add")
String addOrder();
}
@FeignClient 注解就是用来标记该接口是一个 Feign 远程调用客户端,value 属性对应的是服务名称,代表需要调用那个服务中的接口
FeignClient 注解的常用属性如下:
- name:指定 FeignClient 的名称,如果项目使用了 Ribbon, name 属性会作为微服务的名称,用于服务发现。
- url:url 一般用于调试,可以于动指定@FeignClient 调用的地址。
- decode404: 当发生 404 错误时,如果该字段为 true,会调用 decoder 进行解码,否则抛出 FeignException。
- configuration:Feign 配置类,可以自定义 Feign 的 Encoder、 Decoder、 LogLevel 、 Contract。
- fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
- fallbackFactory:工厂类,用于生成 fallback 类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
- path:定义当前 FeignClient 的统一前缀。
自定义 Feign 的配置
日志配置
有时候遇到接口调用失败,参数没收到等问题,或者想看看调用性能。这个时候就需要配置 Feign 的日志级别,让 Feign 把请求信息输出到日志中。代码如下:
public class FeignConfig {
/**
* 日志级别
* NONE: 不输出日志
* BASIC: 只输出请求方法的 URL 和响应的状态码以及接口执行的时间
* HEADERS:将 BASIC 信息和请求头信息输出
* FULL:输出完整的请求信息
* @return
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
然后在 @FeignClient 注解中指定使用的配置类
@FeignClient(value = "order-service",configuration = FeignConfig.class)
public interface OrderClient {
@GetMapping("/order/add")
String addOrder();
}
在配置文件中指定 Client 类的日志级别,格式为"logging.level.client类路径"
logging:
level:
com:
yuxuan:
feign:
OrderClient: debug
FeignConfig 类上如果添加 @Configuration 注解,则不需要在 Client 上再次指定配置类,默认会作用到所有的 FeignClien 上
契约配置
Spring Cloud 在 Feign 的基础上做了扩展,可以让 Feign 支持 Spring MVC 的注解来调用。如果想在 Spring Cloud 中使用原生的注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud 中默认的是 SpringMvcContract。
@Bean
public Contract feignContract(){
return new feign.Contract.Default();
}
Basic 认证配置
通常调用的接口都是有权限控制的,很多时候可能认证的值是通过参数去传递的,还有就是通过请求头去传递认证信息,比如 Basic 认证方式。在 Feign 中我们可以直接配置 Basic 认证。
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
return new BasicAuthRequestInterceptor("user","password");
}
也可以自定义请求拦截器,通过实现 RequestInterceptor 接口来实现。
/**
* feign 请求拦截器
* @Author yuxuan
**/
@Component
public class CustomerFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 具体业务逻辑
}
}
超时时间配置
Feign 的调用分两层,Ribbon 调用和 Hystrix 的调用,高版本的 Hystrix 默认是关闭的。
如果出现上面报错信息,代表 Ribbon 处理超时,添加 Ribbon 配置即可:
ribbon:
ConnectTimeout: 2000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
如果出现上面报错信息,则代表 Hystrix 超时报错,设置 Hystrix 配置即可:
feign
hystrix:
enabled: true
hystrix:
shareSecurityContext: true
command:
default:
circuitBreaker:
sleepWindowInMilliseconds: 100000
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
通过 Options 可以配置连接超时时间和读取超时时间,Options 的第一个参数是连接超时时间(ms),默认值是 10 × 1000 ; 第二个是取超时时间(ms),默认 60 × 1000
@Bean
public Request.Options options(){
return new Request.Options(5000,10000);
}
客户端组件配置
Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient, OkHttp。
配置 OkHttp 只需要加入 OkHttp 的依赖。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
修改配置,将 Feign 的 HttpClient 禁用,启用 OkHttp
feign:
httpclient:
enabled: false
okhttp:
enabled: true
关于配置可参考源码 org.springframework.cloud.openfeign .FeignAutoConfiguration
添加配置之后就会创建默认的 OkHttpClient 对象,当然也可以构建自定义的 OkHttpClient,代码如下。
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//连接超时时间
.connectTimeout(60,TimeUnit.SECONDS)
//读超时时间
.readTimeout(60,TimeUnit.SECONDS)
//写超时时间
.writeTimeout(60,TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool())
.build();
}
}
GZIP 压缩配置
开启压缩可以有效节约网络资源,提升接口性能,我们可以配置 GZIP 来压缩数据
feign:
compression:
request:
##开启请求数据压缩
enabled: true
## 指定压缩类型
mime-types: text/xml,application/xml,application/json
## 最小压缩值
min-request-size: 2048
response:
##开启响应数据压缩
enabled: true
只有当 Feign 的 HttpClient 不是 okhttp3 的时候,压缩才会生效,配置源码在 org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
核心代码就是 @ConditionalOnMissingBean(type = "okhttp3.OkHttpClient") 表示 Spring BeanFactory 中不包含指定的 bean 时条件匹配, 也就是没有启用 okhttp3 时才会进行压缩配置。
配置文件自定义 Feign 的配置
# 链接超时时间
feign.client.config.feignName.connectTimeout=5000
# 读取超时时间
feign.client.config.feignName.readTimeout=5000
# 日志等级
feign.client.config.feignName.loggerLevel=full
# 重试
feign.client.config.feignName.retryer=com.example.SimpleRetryer
# 拦截器
feign.client.config.feignName.requestInterceptors[0]=com.example.FooRequestInterceptor
feign.client.config.feignName.requestInterceptors[1]=com.example.BarRequestInterceptor
# 编码器
feign.client.config.feignName.encoder=com.example.SimpleEncoder
# 解码器
feign.client.config.feignName.decoder=com.example.SimpleDecoder
# 契约
feign.client.config.feignName.contract=com.example.SimpleContract
feignName 需要替换成指定的 feignclient 名称