前文介绍了实现客户端负载均衡的Ribbon,但直接使用Ribbon的API来实现服务间的调用相对较为繁琐,服务间的调用能否像本地接口调用一样便捷、透明,更符合编程习惯呢?Feign就是用来干这事的。
Feign
Feign是一个声明式的Web服务客户端,让服务之间的调用变得非常简单——定义带@FeignClient注解的接口,本地直接@Autowired 接口,通过调用接口的方法来实现远程服务的调用。
支持的注解包括Feign注解与JAX-RS(Java API for RESTful Web Services)注解。
每一个Feign的客户端都包含一系列对应的组件,Spring Cloud通过FeignClientsConfiguration 为每一个命名的Feign客户端创建一个组件集合,包括feign.Decoder,feign.Encoder,feign.Contract等。
Feign提供的默认bean实现及说明
Bean类型 | 默认实现类 | 说明 |
---|---|---|
Decoder | ResponseEntityDecoder | ResponseEntityDecoder封装了SpringDecoder,解码器,将服务的响应消息进行解码 |
Encoder | SpringEncoder | 编码器 |
Logger | Slf4jLogger | 日志框架 |
Contract | SpringMvcContract | 支持注解契约,使用SpringMvcContract可以对Spring MVC注解提供支持 |
Feign.Builder | HystrixFeign.Builder | 使用断路器来装饰Feign接口 |
Client | LoadBalancerFeignClient | 如果是ribbon则 LoadBalancerFeignClient, 如果是spring cloud LoadBalancer 则 FeignBlockingLoadBalancerClient,默认ribbon |
跟Ribbon类似,可以通过配置类来自定义Feign客户端,如
@FeignClient(name = "hello-service", configuration = CustomConfiguration.class) public interface StoreClient { //.. } public class CustomConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } }
这样Feign客户端就包含了FeignClientsConfiguration 与CustomConfiguration 中定义的组件,并且后者会覆盖前者(即自定义配置的优先级高于默认配置)。
自定义配置类不需要加注解@Configuration,如果加了且被@ComponentScan扫描到,则将成为所有Feign客户端的默认配置
同样Feign客户端也支持通过配置文件来配置
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
对于应用于所有Feign客户端的全局默认配置,也可以通过两种方式
-
通过@EnableFeignClients 的defaultConfiguration 属性指定默认配置类
-
在配置文件中通过名称为default的配置实现
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
优先级同Ribbon, 配置文件>自定义配置类>默认的FeignClientsConfiguration
案例演示
本文案例演示基于前面搭建的springcloud-eureka 与 springcloud-eureka-client 两个示例项目。
-
新建springcloud-feign项目,pom.xml中加入依赖
<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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
spring-cloud-starter-openfeign 包含了spring-cloud-starter-netflix-ribbon 与 spring-cloud-starter-loadbalancer。
-
启动类加上@EnableFeignClients 注解
@SpringBootApplication @EnableFeignClients public class FeignApplication { public static void main(String[] args) { SpringApplication.run(FeignApplication.class, args); } }
-
定义Feign client(feign client支持继承)
@FeignClient("hello-service") public interface HelloClient extends BaseHelloClient{ @RequestMapping("hello/param") String hello(@SpringQueryMap QueryParam param); }
-
调用Feign client
@RestController public class FeignController { @Autowired private HelloClient helloClient; @RequestMapping("feign") public String feignTest(){ return "调用Hello-service返回:" + helloClient.hello(); } @RequestMapping("feign/param") public String feignTestParam(QueryParam param) { return "调用Hello-service返回:" + helloClient.hello(param); } }
依次启动三个项目,调用http://localhost:8083/feign 能正常返回调用hello-service的结果。
本示例项目还通过@SrpingQueryMap 注解实现了Feign对 pojo用于GET请求参数的支持。如果不加@SrpingQueryMap, 则pojo参数是无法通过Feign client传递的,可去掉注解自行验证下。
一些知识点
-
如果需要定制化产生的查询参数map,可以实现并注入一个自定义的 QueryMapEncoder bean
-
Feign client的日志可通过feign client接口的全路径名进行配置,如logging.level.project.user.UserClient: DEBUG,默认为NONE(即不打印日志)。全局设置
@Configuration public class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
可设置的level值
-
-
NONE:不记录日志 ,默认
-
BASIC:只记录请求方法,url,以及响应状态码与执行时间
-
HEADERS:包括BASIC与请求、响应头
-
FULL:包括请求与响应的headers,body,metadata
-
-
Feign默认使用Ribbon来做负载均衡,可通过配置spring.cloud.loadbalancer.ribbon.enabled=false 来使用spring cloud loadbalancer(目前Ribbon处于维护状态,近期内不做更新)
-
可通过配置feign.okhttp.enabled=true 或 feign.httpclient.enabled=true 来使用OkHttpClient 或ApacheHttpClient, 默认使用的是JDK 原生的URLConnection 发送HTTP请求,没有连接池
-
如果需要在RequestInterceptor 中使用ThreadLocal中的变量, 那么要么禁用Hystrix,要么设置hystrix的线程隔离策略为SEMAPHORE
feign: hystrix: enabled: false # 或者 hystrix: command: default: execution: isolation: strategy: SEMAPHORE
-
使用有Hystrix fallback的Feign时,会在ApplicationContext中存在多个同类型bean, 导致@Autowired 失效。为了解决这个问题,Spring cloud netflix 将所有feign实例标为@Primary,如果要关闭该特性, 可将@FeignClient的 primary属性置为false。
@FeignClient(name = "hello", primary = false) public interface HelloClient { // ... }
本文示例代码:https://github.com/ronwxy/springcloud-demos
认真生活,快乐分享
欢迎关注微信公众号:空山新雨的技术空间