什么是Feign
Feign是一个声明式Web Service客户端。
使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。
Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign的组成
接口 | 作用 | 默认值 |
---|---|---|
Feign.Builder | Feign的入口 | Feign.Builder |
Client | Feign底层用什么去请求 | 和Ribbon配合时:LoadBalancerFeignClient(代理模式,可以为Feign引入连接池) 不和Ribbon配合时:Fgien.Client.Default(URLConnection,没有连接池,没有资源管理,性能较差) |
Contract | 契约,注解支持 | SpringMVCContract |
Encoder | 解码器,用于将独享转换成HTTP请求消息体 | SpringEncoder |
Decoder | 编码器,将相应消息体转成对象 | ResponseEntityDecoder |
Logger | 日志管理器 | Slf4jLogger |
RequestInterceptor | 用于为每个请求添加通用逻辑 | 无 |
整合Feign
在pom.xml文件中添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
在启动类上添加@EnableFeignClients注解
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import tk.mybatis.spring.annotation.MapperScan; // 扫猫Mybatis哪些包里面的接口 @MapperScan("com.example") @SpringBootApplication @EnableFeignClients public class Study01Application { public static void main(String[] args) { SpringApplication.run(Study01Application.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
使用Feign实现远程http调用
创建feignClient包,用来存放Feign接口,因为我整合过程中调用的是comment接口,所以创建CommentFeignClient接口类。
添加注解@FeignClient,表明这个接口是一个FeignClient,指定name,name是要请求的微服务的名称。
这时,就可以在接口中添加方法了,方法注解参照spring mvc的注解,代码如下:
@FeignClient(name = "study02") public interface CommentFeignClient { @GetMapping("/find") DemoComment find(); }
在前文中,微服务A如果要调用微服务B的请求,参考代码如下:
private final DiscoveryClient discoveryClient; private final RestTemplate restTemplate; public DemoComment findById() { // 获取请求示例 List<ServiceInstance> instances = discoveryClient.getInstances("study02"); List<String> collect = instances.stream() .map(instance -> instance.getUri().toString() + "/find") .collect(Collectors.toList()); // 随机算法 int i = ThreadLocalRandom.current().nextInt(collect.size()); String targetURL = collect.get(i); DemoComment forObject = restTemplate.getForObject("http://study02/find", DemoComment.class, 1); return forObject; }
其中, DemoComment forObject = restTemplate.getForObject("http://study02/find", DemoComment.class, 1);是具体的请求代码,我们借用RestTemplate接口实现了服务间的请求调用。
那么,利用Feign又如何做到呢?
- 首先,我们将restTemplate注入更换为刚刚添加的CommentFeignClient注入
- 然后直接使用commentFeignClient.***()调用请求就可以了。
代码如下:
private final DiscoveryClient discoveryClient; private final CommentFeignClient commentFeignClient; public DemoComment findById() { DemoComment forObject = commentFeignClient.find(); return forObject; }
Feign的配置
和Ribbon一样,Feign也支持两种配置方式:java代码方式及配置属性模式
Feign支持的配置项
代码支持的配置项
配置项 | 作用 |
---|---|
Logger.Level | 指定日志级别 |
Retryer | 指定重试策略 |
ErrorDecoder | 指定错误解码器 |
Request.Options | 超时时间 |
Collection |
拦截器 |
SetterFactory | 用于设置Hystrix的配置属性,Fgien整合Hystrix才会用 |
属性支持的配置项
feign: client: config: feignName: connectTimeout: 5000 # 相当于Request.Optionsn 连接超时时间 readTimeout: 5000 # 相当于Request.Options 读取超时时间 loggerLevel: full # 配置Feign的日志级别,相当于代码配置方式中的Logger errorDecoder: com.example.SimpleErrorDecoder # Feign的错误解码器,相当于代码配置方式中的ErrorDecoder retryer: com.example.SimpleRetryer # 配置重试,相当于代码配置方式中的Retryer requestInterceptors: # 配置拦截器,相当于代码配置方式中的RequestInterceptor - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor # 是否对404错误解码 decode404: false encode: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract
下面以feign的日志级别自定义为例展示feign的配置
细粒度配置
Java代码
首先,添加Feign配置类,可以添加在主类下,但是不用添加@Configuration。如果添加了@Configuration而且又放在了主类之下,那么就会所有Feign客户端实例共享,同Ribbon配置类一样父子上下文加载冲突;如果一定添加@Configuration,就放在主类加载之外的包。建议还是不用加@Configuration。
import feign.Logger; import org.springframework.context.annotation.Bean; public class DemoFeignConfiguration { @Bean public Logger.Level level() { // 让Feign打印所有请求的细节 return Logger.Level.FULL; } }
然后,给@FeignClient添加配置类
@FeignClient(name = "study02", configuration = DemoFeignConfiguration.class) public interface CommentFeignClient { @GetMapping("/find") DemoComment find(); }
因为示例的是日志级别自定义,而feign的日志级别是建立在feign的接口的日志级别是debug的基础上的,所以需要添加配置属性
logging: level: #需要将FeignClient接口全路径写上 com.example.study01.feignClient.CommentFeignClient: debug
配置属性
feign: client: config: #想要调用的微服务名称 study01: loggerLevel: FULL
全局配置
Java代码
在启动类上为@EnableFeignClients注解添加defaultConfiguration配置
@EnableFeignClients(defaultConfiguration = DemoFeignConfiguration.class)
配置属性
feign: client: config: #将调用的微服务名称改成default就配置成全局的了 default: loggerLevel: FULL
优先级:细粒度属性配置 > 细粒度代码配置 > 全局属性配置 > 全局代码配置
打印出来的信息如下:
2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] <--- HTTP/1.1 200 (425ms) 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] content-type: application/json;charset=UTF-8 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] date: Tue, 22 Oct 2019 06:55:41 GMT 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] transfer-encoding: chunked 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] {"id":1,"typeId":0,"valueId":1152101,"content":"MTE=","addTime":1504933578,"status":0,"userId":15} 2019-10-22 14:55:41.286 DEBUG 31468 --- [nio-8881-exec-1] c.e.s.feignClient.CommentFeignClient : [CommentFeignClient#find] <--- END HTTP (98-byte body) 2019-10-22 14:55:42.142 INFO 31468 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: study02.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
Feign多参数请求构造
GET请求
使用@SpringQueryMap
@FeignClient(name = "study02") public interface CommentFeignClient { @GetMapping("/find") public DemoComment query(@SpringQueryMap DemoComment comment); }
使用 @RequestParam
@FeignClient(name = "study02") public interface CommentFeignClient { @RequestMapping(value = "/find",method = RequestMethod.GET) public DemoComment query(@RequestParam("id") Long id, @RequestParam("name") String name); }
使用Map构建(不推荐)
@FeignClient(name = "study02") public interface CommentFeignClient { @RequestMapping(value = "/find",method = RequestMethod.GET) public DemoComment query(@RequestParam Map<String,Object> map); }
POST请求
服务提供者方法
@PostMapping("/save") public DemoComment save(@RequestBody DemoComment comment){ return null; }
服务调用者
@FeignClient(name = "study02") public interface CommentFeignClient { @RequestMapping(value = "/save",method = RequestMethod.POST) public DemoComment query(@RequestBody DemoComment comment); }
Feign性能优化
配置连接池
apache httpClient
添加依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
添加配置
feign: httpclient: # 让feign使用apache httpclient作请求 enabled: true # feign的最大连接数 max-connections: 200 # feign单个路径的最大连接数 max-connections-per-route: 50
okHttp
加依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>1.10<version> </dependency>
添加配置
httpclient: # feign 最大连接数 max-connections: 200 # feign 单个路径请求的最大连接数 max-connections-per-route: 50 okhttp: enabled: true
合理使用Feign日志
生产环境使用 Logger.Level.BASIC