07-Feign远程调用
作者:互联网
二、Feign远程调用
- 之前利用RestTemplate发起远程调用的代码
- 存在下面的问题
- 代码可读性差,编程体验不统一
- 参数复杂URL难以维护
- Feign是一个声明式的http客户端,官方地址如下所示
- 其作用就是帮助我们优雅地实现http请求的发送,解决上面提到的问题
2.1、Feign替代RestTemplate
2.1.1、引入依赖
-
在order-service服务的pom文件中引入feign的依赖
-
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2.1.2、添加注解
-
在order-service的启动类添加注解开启Feign的功能
-
可以注释掉以下代码
-
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
-
2.1.3、编写Feign的客户端
①、在order-service中创建一个包client
②、新建一个接口
-
1)指定服务的名字
-
2)指定访问地址
-
3)方法参数的注入
-
package cn.coolman.order.client; import cn.coolman.order.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("userservice") // 指定服务的名字 public interface UserFeignClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
③、修改OrderController的代码,使用Feign的客户端去调用
-
package cn.coolman.order.web; import cn.coolman.order.client.UserFeignClient; import cn.coolman.order.pojo.Order; import cn.coolman.order.pojo.User; import cn.coolman.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("order") public class OrderController { @Autowired private OrderService orderService; // @Autowired // private RestTemplate restTemplate; @Autowired private UserFeignClient userFeignClient; @GetMapping("{orderId}") public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) { // 根据id查询订单并返回 Order order = orderService.queryOrderById(orderId); // User user = restTemplate.getForObject("http://localhost:8081/user/" + order.getUserId(), User.class); // User user = restTemplate.getForObject("http://userservice/user/" + order.getUserId(), User.class); User user = userFeignClient.findById(order.getUserId()); order.setUser(user); return order; } }
④、小结
- 这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
- 这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送请求了
2.1.4、测试
- 修改order-service中的OrderService类中的queryOrderByUserId方法,使用Feign客户端代替RestTemplate
- 访问浏览器:http://localhost:8080/order/101
2.1.5、小结
- 使用Feign的步骤
- ①、引入依赖
- ②、在启动类上添加@EnableFeignClients注解,启动Feign服务
- ③、编写FeignClient接口(目标方法的路径,服务的名称)
- ④、使用FeignClient中定义的方法代替RestTemplate
- 查看依赖可以知道Feign底层是以来了Ribbon的包
2.2、自定义配置
-
Feign可以支持很多的自定义配置,如下表所示
-
类型 作用 说明 feign.Logger.Level 修改日志级别 包含四种不同的级别:NONE(默认)、BASIC(记录什么时候发出、什么时候结束)、HEADERS(在basic基础还记录请求头信息)、FULL(记录所有的信息) feign.codec.Decoder 响应结果的解析器 http远程调用的结果做解析,例如解析json字符串为java对象 feign.codec.Encoder 请求参数编码 将请求参数编码,便于通过http请求发送 feign.Contract 支持的注解格式 默认是SpringMVC的注解 feign.Retryer 失败重试机制 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 -
一般情况下,默认值就能满足正常使用,如果需要自定义,只需要创建自定义@Bean覆盖默认Bean即可
-
-
下面以日志为例来演示如何自定义配置
2.2.1、配置文件方式
-
以修改order-service为例
-
①、基于配置文件修改Feign的日志级别可以针对单个要调用的服务
-
feign: client: config: userservice: # 针对某个微服务的配置 loggerLevel: BASIC # 日志级别
-
-
②、也可以针对所有服务
-
feign: client: config: default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置 loggerLevel: FULL # 日志级别
-
-
PS:配置default.loggerLevel的时候idea中没有提示信息,如果两个都要配置,则优先使用userservice
-
-
要看到输出的日志信息,需要将系统日志级别设置为debug才能看到
-
logging: level: cn.coolman: debug
-
feign: client: config: userservice: loggerLevel: BASIC default: loggerLevel: FULL
-
-
日志的级别分为四种
- NONE:不记录任何日志信息,这是默认值
- BASIC:仅记录请求的方法,URL以及响应状态码和执行事件
- HEADERS:在BASIC的基础上,额外记录了请求和响应头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
-
配置完成后,重启服务,在浏览器访问一次,对比以下配置前和配置后的控制台输出信息
- 配置前
- 配置后
- 配置前
2.2.2、Java代码方式
将上面的配置注释
-
①、也可以基于Java代码来修改日志级别,先声明一个类放在config包中,然后声明一个Logger.Level的对象
-
package cn.coolman.order.config; import feign.Logger; import org.springframework.context.annotation.Bean; public class DefaultFeignConfiguration { @Bean public Logger.Level loggerLevel() { // 日志级别为BASIC,这是枚举类型 return Logger.Level.BASIC; } }
-
-
②、如果要全局生效,将其放到OrderApplication启动类的@EnableFeignClient这个注解中
-
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
-
-
③、如果是局部生效,则把它放到UserClient接口对应的@FeignClient这个注解中,将上面OrderApplication启动类的defaultConfiguration部分去掉
-
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
-
-
④、查看控制台输出信息
2.2.3、小结
- Feign日志的类型
- NONE:默认,不记录日志
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC基础上添加请求头
- FULL:记录全部
- 如何实现Feign的日志记录
- yml文件配置
- 编码方式配置
2.3、Feign使用优化
- Feign底层发起http请求,依赖于其他的框架,其底层客户端实现包括
- URLConnection:默认实现,不支持连接池。每次出啊关键连接要三次握手,结束连接还要四次挥手
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
- 因此提高Feign的性能的主要手段就是使用连接池代替默认的URLConnection
2.3.1、Apache的HttpClient
-
1)引入依赖
-
在order-service的pom文件中引入Apache的HttpClient依赖
-
<!--httpClient的依赖 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
-
-
2)配置连接池
-
在order-service的application.yml中添加配置,默认是开启的,所以就算没有配置也会有一些默认的配置参数
-
feign: httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数
-
PS:每个不同的微服务连接路径是不同的,如访问user-service这个微服务就是一条路径,最多有50个连接;通常最大连接数除以微服务总数,即可得出每个微服务平均占用多少个连接。后期可以根据访问性能的变化来进行调整
-
-
3)在FeignClientFactoryBean的loadBalance方法中打断点
- debug方式启动order-service服务
- 可以看到这里的client,未配置pom.xml前使用的是默认的Client
- 配置后底层就是Apache HttpClient
2.3.2、小结
- Feign-HttpClient的作用
- 默认情况Feign发起的远程调用使用的是URLConnection,URLConnection是不支持连接池的,每次远程调用都需要花费大量的时间去建立连接,使用Feign-HttpClient可以对url连接可以存储到连接池中,提高效率
2.4、最佳实践
所谓最佳实践,就是使用过程中总结的经验,得出的一种最好的方式
- 仔细观察不难发现,Feign客户端与服务提供者的Controller代码非常相似
- Feign客户端
- UserController
- Feign客户端
- 思考一种方法简化这种重复的代码编写
2.4.1、继承方式
- 一样的代码可以通过继承来共享
- 1)定义一个API接口,利用定义方法,并基于SpringMVC注解声明
- 2)Feign客户端和Controller都继承该接口
优点
- 编码简单
- 实现了代码共享
缺点
- 服务提供方、服务消费方紧耦合
- 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表的注解
2.4.2、抽取方式
- 将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
- 例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用
①、抽取
-
1)首先创建一个module,命名为feign-api,继承于cloud-demo父模块
-
2)在feign-api中然后引入feign的starter依赖
-
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
-
-
3)在order-service中编写的UserClient、User、DefaultFeignConfiguration都移动到feign-api项目中
-
4)修改代码:UserClient中引入的User和包名DefaultFeignConfiguration不同了,其他类不变
- 跟着idea操作即可,导包
-
5)将feign-api安装到本地仓库(install)
②、使用feign-api
-
1)首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口
-
2)在order-service的pom文件引入feign-api的依赖
-
<dependency> <groupId>cn.itcast.demo</groupId> <artifactId>feign-api</artifactId> <version>1.0</version> </dependency>
-
③、修改order-service
- 1)修改Order
- 2)修改Orderservice
- 3)启动类中如果用到了DefaultFeignConfiguration也要改一下
这三步都是因为导包的问题,比较简单,这里就不演示了,跟着idea操作也能解决
④、重启测试
- 重新运行程序后,发现服务报错了,报错信息如下所示
- 这是因为UserClient现在在
cn.coolman.feign.client
包下,而order-service的@EnableFeignClients
注解默认扫描的是cn.coolman.order
包和它的子包,不在同一个父包中,无法扫描到UserClient
⑤、解决扫描包问题
-
方式一
-
指定Feign应该扫描的包:对整个包下的所有接口生成代理对象
-
@EnableFeignClients(basePackages = "cn.itcast.feign.client")
-
-
方式二
-
指定需要加载的Client接口:对指定的接口生成代理对象
-
@EnableFeignClients(clients = {UserClient.class})
-
-
测试
标签:feign,调用,07,service,Feign,import,order,cn 来源: https://www.cnblogs.com/OnlyOnYourself-lzw/p/16458206.html