Feign简介
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 可以做到使用HTTP请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。Feign的Github网址,比如:
Feign具有如下特性:
- 可插拔的注解支持,包括Feign注解和JAX-RS注解
- 支持可插拔的HTTP编码器和解码器
- 支持Hystrix和它的Fallback
- 支持Ribbon的负载均衡
- 支持HTTP请求和响应的压缩
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。它整合了
Ribbon
和Hystrix
,从而不再需要显式地使用这两个组件。Feign还提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求的参数、格式、地址等信息。接下来,Feign会完全代理HTTP的请求,我们只需要像调用方法一样调用它就可以完成服务请求。Feign 示例工程
链接:https://github.com/SoftwareKing/spring-cloud-study/tree/master/sc-feign-first
本文最终修改时间:2017-05-20 18:47:23,为了解决
问题1和2
最终使用版本:Spring Boot的版本为1.5.3.RELEASE
,Spring Cloud版本为Dalston.RELEASE
服务消费者中sc-feign-first-consumer的Feign的定义
为了让Feign知道在调用方法时应该向哪个地址发请求以及请求需要带哪些参数,我们需要定义一个接口:
|
|
A: @FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入,如下代码所示。
|
|
B: @RequestMapping表示在调用该方法时需要向/sc/order/{id}发送GET请求。
C: @PathVariable与SpringMVC中对应注解含义相同
服务消费者中Feign的使用
|
|
如上代码所示,通过@Autowired将声明的Feign依赖注入即可,调用userFeignService.findOrderById(id)使用。开发者通过userFeignService.findOrderById()就能完成发送HTTP请求和解码HTTP返回结果并封装成对象的过程。
启动测试
依次按顺序启动如下工程
注册中心: sc-fegin-first-server
服务提供者1:sc-fegin-first-provider01
服务提供者2:sc-fegin-first-provider02
以上工程能正常启动work,但是当启动服务消费者: sc-fegin-first-consumer报错如下。
使用的示例工程的Spring Boot的版本为1.5.2.RELEASE,Spring Cloud版本为Dalston.RELEASE会出现以下错误。
|
|
访问http://localhost:8010/sc/user/1 ,出现以下错误即:【问题一】
feign/Feign$Builder
Caused by: java.lang.NoClassDefFoundError: feign/Feign$Builder
经查找解决问题2天查看无果(捂脸,后面写源码分析定位),因此决定将Spring Boot的版本改变为1.4.3.RELEASE,Spring Cloud版本为Camden.SR5之后,按上面的顺序启动,之后测试http://localhost:8010/sc/user/1 ,可以正常work。
Fegin的work原理
Spring Cloud应用在启动时,Feign会扫描标有@FeignClient
注解的接口,生成代理
,并注册到Spring容器中
。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象
,该对象封装了HTTP请求
需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。
在本例中,我们将Feign与Eureka和Ribbon组合使用,@FeignClient(name = “sc-feign-first-provider”)意为通知Feign在调用该接口方法时要向Eureka中查询名为ea的服务,从而得到服务URL。
Fegin的常见应用
Feign的Encoder、Decoder和ErrorDecoder
Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。
默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。
注意,如果在@RequetMapping中的method将请求方式指定为GET,那么所有未标注解的参数将会被忽略,例如:
|
|
此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。
- 在Spring Cloud环境下,Feign的Encoder只会用来编码没有添加注解的参数。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。
- 对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。
- ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。
我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。
更换Feign默认使用的HTTP Client
Feign在默认情况下使用的是JDK原生的URLConnection
发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client
替换Feign原始的http client, 从而获取连接池、超时时间
等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:
|
|
然后在application.yml
中添加如下:
spring cloud feign常见问题
参数不会自动传递
服务消费者端调用
服务提供者Controller对外服务
Fegin客户端定义调用
启动的时候sc-fegin-first-consumer工程不报错。但是当访问http://localhost:8010/test?name=xujin&age=25 ,报错如下
Fegin客户端定义修改如下OK,原因是name被自动放到request body。只要有body,就会被feign认为是post请求,所以整个hello是被当作带有request parameter和body的post请求发送出去了,因此出现上面的错误提示。
|
|
POST多参数调用
- POST多参数 Feign端定义:
|
|
|
|
以上两种定义方式等价
- 服务提供者的定义
|
|
修改订单号返回证明,服务提供者接到从Feign POST请求过来的数据。
- 服务消费者端的使用
|
|
- 测试
当修改了Feign默认的http Client之后,出现如下错误,具体出错原因还在排查之中,本文会随时更改。【问题二】
更换了Feign默认的Client出现HystrixRuntimeException12345678{"timestamp": 1494947172990,"status": 500,"error": "Internal Server Error","exception": "com.netflix.hystrix.exception.HystrixRuntimeException","message": "UserFeignService#post(OrderModel) failed and no fallback available.","path": "/test/post"}
|
|
当关闭之后,访问正常如下所示,醉了同样的代码(PS:捂脸)
|
|
|
|
GET多参数调用
当服务之间GET调用为多参数时,可以使用Map来构建参数传递
Feign接口中的示例定义
服务消费者的调用
|
|
个人看来,如果是GET的多参数通过Map进行传递,当参数比较多时,个人建议使用面向对象的思维,通过POST的方式传递对象相对较好。
服务提供者的使用
|
|
访问URL:http://localhost:8010/test/get ,测试OK.
|
|
总结
本文主要介绍了Feign的基本的定义,以及Feign的work原理和使用Feign的注意事项和常见问题。最后介绍了一下更换Feign默认使用的HTTP Client。主要是遇到一个奇葩的问题,最终没解决更换版本。在下一篇文章中将介绍Feign的其它的使用,例如Feign的继承,日志级别,以及Feign源码分析等
参考文献
希望Feign能够支持参数请求使用POJO的Issue
建议使用Feign原生的注解的Issue
建议增强Feign的功能
建议支持可选的Request Body(目前Feign当POST一个null时,会报异常)