教你使用Feign替换RestTemplate

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,那要是多台要修改的情况下那就得改很多台,当我们服务多的时候这是个很麻烦的事情。

1.Feign替代RestTemplate

Fegin的使用步骤如下:

1)引入依赖

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


  org.springframework.cloud
  spring-cloud-starter-openfeign

2)添加注解

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

教你使用Feign替换RestTemplate_第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地址。

4)测试

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

@Service
public 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替换RestTemplate_第2张图片

2.自定义配置

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

类型

作用

说明

feign.Logger.Level

修改日志级别

包含四种不同的级别:NONE、BASIC、HEADERS、FULL

feign.codec.Decoder

响应结果的解析器

http远程调用的结果做解析,例如解析json字符串为java对象

feign.codec.Encoder

请求参数编码

将请求参数编码,便于通过http请求发送

feign. Contract

支持的注解格式

默认是SpringMVC的注解

feign. Retryer

失败重试机制

请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@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依赖:



  io.github.openfeign
  feign-httpclient

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:

教你使用Feign替换RestTemplate_第3张图片

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

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

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

教你使用Feign替换RestTemplate_第4张图片

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

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

1)抽取

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

项目结构:

教你使用Feign替换RestTemplate_第5张图片

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


  org.springframework.cloud
  spring-cloud-starter-openfeign

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

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

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

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


  cn.itcast.demo
  feign-api
  1.0

修改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请求的路径参数等。

2. 动态代理

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

3. 编码器和解码器

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

4. 负载均衡

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

5. 断路器

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

你可能感兴趣的:(java,spring,cloud,微服务)