写点什么

教你使用 Feign 替换 RestTemplate

  • 2023-05-18
    湖南
  • 本文字数:4608 字

    阅读完需:约 15 分钟

Feign 是 Spring Cloud Netflix 组件中的一个轻量级 Restful 的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了 Ribbon 和 RestTemplate, 实现了 WebService 的面向接口编程,进一步降低了项目的耦合度。


先来看我们以前利用 RestTemplate 发起远程调用的代码:

String url = "http://user-service:8081/user/"+order.getUserId();User user = restTemplate.getForObject(url, User.class);
复制代码

以上的代码存在参数复杂、URL 难以维护等问题,如当我有一台服务地址换了,那么这时候就需要云同步修改 url,那要是多台要修改的情况下那就得改很多台,当我们服务多的时候这是个很麻烦的事情。

一、Feign 替代 RestTemplate

Fegin 的使用步骤如下:

1.1 引入依赖

我们在 order-service 服务的 pom 文件中引入 feign 的依赖:

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
复制代码

1.2 添加注解

在 order-service 的启动类添加注解开启 Feign 的功能:


1.3 编写 Feign 的客户端

在 order-service 中新建一个接口,内容如下:

package cn.itcast.order.service.feign;
import cn.itcast.order.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("user-service")public interface UserFeignClient { @GetMapping("/user/{id}") User queryById(@PathVariable("id") Long id);}
复制代码

这个客户端主要是基于 SpringMVC 的注解来声明远程调用的信息,比如:

  • 服务名称:user-service

  • 请求方式:GET

  • 请求路径:/user/{id}

  • 请求参数:Long id

  • 返回值类型:User


这样,Feign 就可以帮助我们发送 http 请求,无需自己使用 RestTemplate 来发送了。底层会通过服务名称:user-service 去映射到具体的 user 服务对应的 url 地址。

1.4 测试

修改 order-service 中的 OrderService 类中的 queryOrderById 方法,使用 Feign 客户端代替 RestTemplate:

@Servicepublic class OrderService {
@Autowired private OrderMapper orderMapper;
@Autowired private RestTemplate restTemplate;
@Autowired UserFeignClient feignClient;
public Order queryOrderById(Long orderId) { // 1.查询订单 Order order = orderMapper.findById(orderId); User user = feignClient.queryById(orderId); order.setUser(user); // 4.返回 return order; }}
复制代码

测试调用:

二、自定义配置

Feign 可以支持很多的自定义配置,如下表所示:

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的 @Bean 覆盖默认 Bean 即可。


下面以日志为例来演示如何自定义配置。

2.1 配置文件方式

基于配置文件修改 feign 的日志级别可以针对单个服务:(注意:有时 yml 配置文件中有中文注释会报错)

feign:    client:    config:       user-service: # 针对某个微服务的配置        loggerLevel: FULL #  日志级别
复制代码

也可以针对所有服务:

feign:    client:    config:       default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置        loggerLevel: FULL #  日志级别
复制代码

而日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL 以及响应状态码和执行时间

  • HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

2.2 Java 代码方式

也可以基于 Java 代码来修改日志级别,先声明一个类,然后声明一个 Logger.Level 的对象:

public class DefaultFeignConfiguration  {    @Bean    public Logger.Level feignLogLevel(){        return Logger.Level.BASIC; // 日志级别为BASIC    }}
复制代码

如果要全局生效,将其放到启动类的 @EnableFeignClients 这个注解中:

package cn.itcast.order;
import cn.itcast.order.config.DefaultFeignConfiguration;import com.netflix.loadbalancer.IRule;import com.netflix.loadbalancer.RandomRule;import org.mybatis.spring.annotation.MapperScan;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;
@MapperScan("cn.itcast.order.mapper") @SpringBootApplication // 配置类的方式开启全局日志记录 //@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) // 开启feign客户端的支持 @EnableFeignClients // 开启feign客户端的支持 public class OrderApplication {
public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }
//...... }
复制代码

如果是局部生效,则把它放到对应的 @FeignClient 这个注解中:

package cn.itcast.order.feign;
import cn.itcast.order.config.DefaultFeignConfiguration;import cn.itcast.order.pojo.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;// 指定服务日志配置//@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)@FeignClient(value = "userservice") public interface UserFeignClient { @GetMapping("/user/{id}") User queryById(@PathVariable("id") Long id); }
复制代码

2.3 Feign 使用优化

Feign 底层发起 http 请求,依赖于其它的框架。其底层客户端实现包括:

  • URLConnection:默认实现,不支持连接池

  • Apache HttpClient :支持连接池

  • OKHttp:支持连接池


因此提高 Feign 的性能主要手段就是使用连接池代替默认的 URLConnection。


这里我们用 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:  client:    config:      default: # default全局的配置        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息  httpclient:    enabled: true # 开启feign对HttpClient的支持    max-connections: 200 # 最大的连接数    max-connections-per-route: 50 # 每个路径的最大连接数
复制代码

max-connections 解释:比如有以下情况,有一个服务 A 同时有可能会访问 B 服务和 C 服务,这时候配置的最大连接数指的就是 A 在访问 B 和 C 时,总的连接数不超过 200。


max-connections-per-route 解释:指的是 A 服务访问 B 服务时的路径最大连接数据为 50,也就是 200 个连接,A 服务到 B 服务的访问最多只会有 50 个连接,当超出 50 个连接时,其他连接就会路由到 B 服务之外的服务中。


接下来,在 FeignClientFactoryBean 中的 loadBalance 方法中打断点:


Debug 方式启动 order-service 服务,可以看到这里的 client,底层就是 Apache HttpClient:

改成 http 连接池后,从演示项目后台的请求日志中可以发现会从原来的几十 ms 变成个位数 ms,有兴趣的小伙伴可以自己测试一下。

2.4 最佳实践-抽取 feign-api 接口

目前存在一个问题,我们目前演示的是只有一个 order-service 调用 user-service,那如果当有多个服务要去调 userservice 的时候呢,那是否需要在每个 service 里都去写一份远程调用 user-service 的代码?完成没必要是不是?所以,把这部分代码直接抽成一个 module 打成 jar 包,在需要调用的地方引入即可。

将 Feign 的 Client 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用。


即将 UserClient、User、Feign 的默认配置都抽取到一个 feign-api 包中,所有微服务引用该依赖包,即可直接使用。

1)抽取

首先创建一个 module,命名为 feign-api:


项目结构:

在 feign-api 中然后引入 feign 的 starter 依赖

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
复制代码

然后,order-service 中编写的 UserClient、User、DefaultFeignConfiguration 都复制到 feign-api 项目中

2)在 order-service 中使用 feign-api

首先,删除 order-service 中的 UserClient、User、DefaultFeignConfiguration 等类或接口。

在 order-service 的 pom 文件中中引入 feign-api 的依赖:

<dependency>  <groupId>cn.itcast.demo</groupId>  <artifactId>feign-api</artifactId>  <version>1.0</version></dependency>
复制代码

修改 order-service 中的所有与上述三个组件有关的导包部分,改成导入 feign-api 中的包

扫描包问题


最后,包的扫描要指定一下,不然启动会报错找不到 userfeignclient


方式一:

指定 Feign 应该扫描的包:@EnableFeignClients(basePackages = "cn.itcast.feign.clients")


方式二:

指定需要加载的 Client 接口:@EnableFeignClients(clients = {UserClient.class})

Feign 实现原理


Feign 的底层源码实现主要包括以下几个部分:


  1. 接口定义

Feign 的接口定义类似于 Java 的接口定义,但是它使用了注解来描述 HTTP 请求的参数和返回值。例如,@RequestMapping 注解用于指定 HTTP 请求的 URL 和请求方法,@RequestParam 注解用于指定 HTTP 请求的参数,@RequestBody 注解用于指定 HTTP 请求的请求体,@PathVariable 注解用于指定 HTTP 请求的路径参数等。


  1. 动态代理

Feign 使用了 Java 的动态代理技术来生成 HTTP 请求的实现类。当应用程序调用 Feign 接口的方法时,Feign 会动态生成一个 HTTP 请求的实现类,并将请求参数传递给该实现类。该实现类会将请求参数转换为 HTTP 请求,并发送给远程服务。当远程服务返回响应时,该实现类会将响应转换为 Java 对象,并返回给应用程序。


  1. 编码器和解码器

Feign 使用了编码器和解码器来将 Java 对象转换为 HTTP 请求和响应。编码器将 Java 对象转换为 HTTP 请求的请求体,解码器将 HTTP 响应的响应体转换为 Java 对象。Feign 支持多种编码器和解码器,例如 JSON 编码器和解码器、XML 编码器和解码器等。


  1. 负载均衡

Feign 可以与负载均衡器无缝集成,以实现服务的负载均衡。当应用程序调用 Feign 接口的方法时,Feign 会将请求发送给负载均衡器,负载均衡器会选择一个可用的服务实例,并将请求转发给该实例。如果该实例不可用,则负载均衡器会选择另一个可用的服务实例,并将请求转发给该实例。


  1. 断路器

Feign 可以与断路器无缝集成,以实现服务的容错。当应用程序调用 Feign 接口的方法时,Feign 会将请求发送给断路器,断路器会检查服务实例的可用性。如果服务实例不可用,则断路器会返回一个默认的响应,以避免应用程序出现异常。如果服务实例可用,则断路器会将请求转发给该实例,并返回实例的响应。


作者:轻洲技术

链接:https://juejin.cn/post/7232846086833963067

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
教你使用Feign替换RestTemplate_做梦都在改BUG_InfoQ写作社区