一、先看在此之前,我们服务之间需要进行调用的时候使用的restTemplate,代码示例如下:
String url = "http://userservice/user/"+userId; User user = restTemplate.getForObject(url , User.class);
这种方法需要先定义一个url,再使用restTemplate的api向这个路径去发送请求
思考这种方式的缺陷:在实际开发中,一个url会有很复杂的情况出现,参数可能多达几十个,此时要维护一个url将是一件很恐怖的事情。其次,代码的可读性也比较差
因此,有一种新的方式去发起远程调用,也就是Feign,feign是一种声明式的http客户端,其作用就是帮助开发者优雅的实现远程调用,极其优雅
二、使用方式
1、引入Feign的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2、在服务消费者启动类上添加@EnableFeignClient注解,开启Feign的功能
3、编写Feign的客户端(在服务消费者中新建一个接口,内容如下)
@FeignClient("userservice") //这个注解的默认属性是name/value,值是服务提供者的服务名称 public interface UserClient { @GetMapping("/user/{id}") //这里是要调用服务提供者的接口的地址 User findById(@PathVariable("id") Long id); //这里是标识服务提供者的接口的方法,方法名可以不一样,返回值、参数列表必须一样 }
这个客户端主要是基于springMVC的注解来声明远程调用的信息,比如:
* 请求方式:GET
* 请求路径:/user/{id}
* 请求参数:Long id
注:FeignClient注解属性的说明
4、发起调用
在需要调用的地方注入定义浩的接口(这里是UserClient),再用注入的接口对象去调用对应的 "方法"
@Resource
private UserClient userClient;
@GetMapping("{orderId}") public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) { // 根据id查询订单并返回 Order order = orderService.queryOrderById(orderId);
//restTemplate方式远程调用 //定义请求的路径 //String url = "http://userservice/user/"+order.getUserId(); //发送get请求 //User user = restTemplate.getForObject(url, User.class); //Feign方式远程调用 User user1 = userClient.getById(order.getUserId());
order.setUser(user1); return order; }
三、总结使用方法:
①引入依赖
②再服务消费者的启动类上添加@EnableFeignClients注解
③编写FeignClient接口
④使用FeignClient接口中定义的 ”方法“ 远程调用
四、Feign的自定义配置
1、Feign可以支持很多的自定义配置,大概有如下这些:
作用 | 说明 | |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可
2、以日志为例,实现自定义配置(两种方式)
①基于配置文件修改feign的日志级别(可针对某个服务,也可针对所有的服务):
feign: client: config: userservice: # 如果针对某个服务,这里就写对应的服务名,如果针对所有服务,就写default loggerLevel: FULL # 日志的级别(有NONE、BASIC、HEADERS、FULL)
日志的级别分为四种:
NONE:不记录任何日志信息,这是默认值
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
②基于Java代码,在项目中声明一个类,将Logger.Level对象注册到spring容器中
public class DefaultFeignConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; // 日志级别为BASIC(枚举类) } }
Ⅰ、如果想要自定义配置全局生效,则在启动类的@EnableFeignClients注解中放入这个类
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
Ⅱ、如果想要自定义配置局部生效,则在对应的@FeignClient注解中放入这个类
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
五、Feign的优化建议:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此,想要提升Feign的性能主要手段就是使用连接池代替默认的URLConnection
2、实例使用Apache HttpClient
①引入HttpClient的依赖
<!--httpClient的依赖 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
②在配置文件中配置连接池的一些配置:
feign: client: config: default: # default全局的配置 loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息 httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数
③总结Feign的优化建议:
Ⅰ、日志级别尽量使用BASIC或者使用NONE
Ⅱ、使用带连接池的Feign客户端
六、Feign的最佳实践(所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式)
①使用过程中我们可以发现,Feign的客户端与服务提供者的controller代码非常相似,所以我们利用继承的方式来抽取共同的部分:
Ⅰ、定义一个API接口,利用定义方法,基于springMVC注解做声明
Ⅱ、Feign客户端和Controller都继承该接口
Ⅲ、不足之处:
服务提供方、服务消费方紧耦合
参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
②抽取方式
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
③抽取步骤
Ⅰ、创建一个module,命名为feign-api
Ⅱ、在feign-api的pom中引入依赖(包括geign的starter依赖和Apache HttpClient依赖)
Ⅲ、将之前服务消费者中编写的UserClient、User、DefaultFeignConfiguration都提取到feign-api子项目中
Ⅳ、在原先的服务消费者中使用抽取的feign-api
1、首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口
2、在服务的消费者中引入feign-api的依赖
3、修改服务中所有与上述三个组件有关的部分,改成feign-api包中引入
4、重启,(发现会报错)
报错原因:UserClient在另一个项目的包中,所以服务的消费者无法扫描到UserClient组件
解决方式:(两种)
①指定Feign应该扫描的包(不建议使用,这样把没用到的也引入了,造成浪费)
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
②指定需要加载的Client的接口:(推荐使用,按需引用)
@EnableFeignClients(clients = {UserClient.class})