7-1 使用Feign实现远程HTTP调用
1)什么是Feign
- Feign是Netflix开源的声明式HTTP客户端
- Feign的Github地址:Feign
2)使用Feign重构前面的代码
之前代码架构存在的问题
三板斧
重构代码的流程(以 /shares/{id}接口为例):
- 服务提供者user-center编写相应接口UserController,实现根据id获取UserDTO的方法findUserById()
- 服务消费者content-center声明一个UserFeignClient,去调用UserController的方法
- ShareService注入UserFeignClient的实例,获取到其调用其他微服务的能力,从而将ShareMapper和UserFeignClient提供的数据进行拼装,得到ShareDTO
- ShareController注入ShareService实例,调用即可
老报数据库连接实例过多的问题,解决方法
7-2 Feign的组成
Feign使用很简单,但它的源码中大量充满各种设计模式,比如建造者模式、代理模式等等
以下表格整理了Feign的组成
通过以下方法查看Feign的底层代码,比如默认的Feign.Default
采用的是HttpUrlConnetion,它没有连接池,也没有资源管理的概念,性能比较差
再来看下LoadBalancerFeignClient
可以看到它使用了代理模式,我们可以传入任何的Client,默认传的就是上面那种Default
我们可以认为LoadBalancerFeignClient是一个带有负载均衡的Client.Default,它也用Url去请求,到性能优化的部分,我们可以改为其他方式如HttpClient来请求,引入连接池等 。
Contract:Feign默认是不支持SpringMVC注解的,它默认的注解如下图:
Spring Cloud对Feign进行了一些扩展,使之支持了SpringMVC的注解。
7-3 细粒度配置自定义
两种方式:
- Java代码方式
- 配置属性方式
以Feign的日志打印为例来学习以上两点
1)Java代码方式
- 先写一个自定义的类,返回feign的日志级别,头部不要加@Configuration注解,否则会父子上下文重叠,必须挪到外面的包

- 在UserCenterFeignClient中,通过@FeignClient注解的configuration属性使用它

- application.yml中配置feign的日志

- 访问内容中心接口,可以看到详细日志信息
2)配置属性方式
超级简单,只需如下一行配置
feign.client.config.
先去掉配置属性
然后在yml文件中加入以下配置即可
重新运行,一样可以看到请求的详细日志结果,这种方式更简单
7-4 全局配置
1)Java代码方式

方式一:让父子上下文omponentSean重叠(强烈不建
议使用于
方式二(唯一正确的途径
@EnableFeignclients(defaultConfigurationxxX.class)
- 注释掉细粒度配置

- 启动主类注解加上defaultConfiguration属性,指向配置类即可(基于语义,类名使用GlobalFeignConfiguration,代码同UserCenterFeignConfiguration)
2)配置属性方式
- 注释掉启动类的defaultConfiguration属性

- yml中加入如下配置,用default代替之前的user-center即可实现全局配置
7-5 支持的配置项
1)代码方式支持的配置项
2)属性方式支持的配置项
7-6 配置总结
1)Ribbon配置 VS Feign配置
2)Feign代码方式 VS 属性方式
3)最佳实践总结
- 尽量使用属性配置,属性方式实现不了的情况再考虑用代码配置
- 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性
7-7 Feign的继承
7-8 多参数请求的构造
1)GET请求
- 用户中心Controller增加方法,多个参数

- 启动用户中心,没有错误
浏览器测试,通过url参数的构造,使得传入的参数封装到请求体,最后返回该实体
- 内容中心相关代码


- 重启运行,应用启动报错

- 按照建议,在yml中加入这个配置

- 再次启动,可以成功

WHY???
因为出现了两个 “user-center”同名的FeignClient,加上上述配置就可以允许同名的存在**
- 现在,通过如下url访问测试接口,发现报错

控制台报了405异常
如何解决呢?
方法一(推荐)
很简单,在FeignClient方法参数前加一个注解就可以了
再次启动后验证结果
方法二(推荐)
使用@RequestParam注解在接口中构造参数
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient {@RequestMapping(value = "/get", method = RequestMethod.GET)public User get1(@RequestParam("id") Integer id, @RequestParam("username") String username);}
方法三(不推荐)
多参数的URL也可使用Map来构建。当目标URL参数非常多的时候,可使用这种方式简化Feign接口的编写。
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient {@RequestMapping(value = "/get", method = RequestMethod.GET)public User get2(@RequestParam Map<String, Object> map);}
在调用时,可使用类似以下的代码
public User get(String username, String password) {HashMap<String, Object> map = Maps.newHashMap();map.put("id", "1");map.put("username", "张三");return this.userFeignClient.get2(map);}
注意:这种方式不建议使用。主要是因为可读性不好,而且如果参数为空的时候会有一些问题,例如map.put("username", null); 会导致microservice-provider-user 服务接收到的username是"" ,而不是null。
2)POST请求
假设服务提供者的Controller是这样编写的
@RestControllerpublic class UserController {@PostMapping("/post")public User post(@RequestBody User user) {...}}
我们要如何使用Feign去请求呢?答案非常简单,示例如下:
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient {@RequestMapping(value = "/post", method = RequestMethod.POST)public User post(@RequestBody User user);}
7-9 Feign脱离Ribbon使用
现在用Feign调用的微服务都是注册到Nacos上的,如何使用Feign调用没有注册到Nacos的服务呢,比如调用百度,如何实现?
写BaiduFeignClient
注意:@FeignClient注解,要么指定value,要么指定name,否则会报错!

测试结果
7-10 RestTemplate VS Feign

如何选择?
- 尽量使用Feign,代码优雅
- 合理选择
7-11 Feign性能优化
7-12 常见问题总结
看这篇
7-13 现有架构总结
微服务架构初具雏形





