FFeign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:
Feign 的设计大致如下:
使用了 Feign 后,我们不用再去构造 HTTP 需要的信息给 Client,直接定义一个接口,然后在接口上面添加注解,Feign 会去解析这个接口的注解声明,解析出 MethodHandler。
有了 Feign,使得编写 Java Http 客户端变得更容易,在前面章节中,使用 Ribbon + RestTemplate 来形成一套模板化的调用方法。在实际开发中,对于服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常会针对每个微服务自行提供一些客户端类来包装这些依赖服务的调用。
所以,Feign 在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义,在 Feign 的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用 SpringCloud Ribbon 时,自动封装服务调用客户端的开发量。
同时,Feign 集成了 Ribbon,可以利用 Ribbon 维护 服务列表信息,并且通过轮询实现客户端的负载均衡。而与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
上面说的很绕,下面通过一个图来解释:
使用 RestTemplate 时是这样的工作流程:
使用 Feign ,就变成这样的工作流程:
消费方的 Service 可不是用 RestTemplate 来调用,而是直接注解的方式来声明,下面会通过实操来了解。
Feign 和 OpenFeing 的区别
Feign | OpenFeign |
---|---|
SpringCloud 组件中的一个轻量级 RestFul 的 Http 服务客户端,内置了 Ribbon 来做客户端负载均衡。使用方式是通过注解定义接口,调用这个接口就可以调用服务注册中心的服务。 | SpringCloud 在 Feign 基础上支持 SpringMVC 的注解,如@RequestMapping等。它的 @FeignClient 可以解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
Github地址:https://github.com/spring-cloud/spring-cloud-openfeign
OpenFeign 是作用在消费方的,比如我一个消费方的端口是 80,现在要使用 OpenFeign 实现对 8001 模块的服务调用。
首先在消费方模块导入依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
通过上面的介绍可知,OpenFeign 是在 Feign 的基础上增加了 SpringMVC 的支持。因此我们直接使用 OpenFeign。
编写配置文件 application.yaml:
server:
port: 80
spring:
application:
name: cloud-feign-order
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
然后消费方主启动类中声明使用 Feign:
package pers.klb.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 启动feign
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
接着就写一个消费方的 service:
package pers.klb.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import pers.klb.springcloud.entities.CommonResult;
import pers.klb.springcloud.entities.Payment;
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getById(@PathVariable("id") Long id);
}
最后在消费方的控制器中调用上面这个 service:
package pers.klb.springcloud.controller;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import pers.klb.springcloud.entities.CommonResult;
import pers.klb.springcloud.entities.Payment;
import pers.klb.springcloud.service.PaymentFeignService;
@RestController
@Slf4j
public class OrderFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getById(id);
}
}
可以看出,整个使用流程是很熟悉的,唯一的不同点就是消费方的 service 不是操作 dao,而是去调用服务。
首先,通过配置文件,Eureka 能获取到所有的 Eureka Server,进而获取到能访问到的所有服务和每个服务对应的访问地址集群。
接着,消费方的 service 声明了服务的名称 CLOUD-PAYMENT-SERVICE,通过 ribbon 的负载均衡获取这个服务的其中一个地址,即 8001 或者 8002,接着声明了服务的调用方式为 get,uri 为 /payment/get/{id},那么,Feign 就会去 8001 模块或者 8002 模块访问对应的接口。
消费方请求服务方,服务方处理这个请求是需要时间的,OpenFeign 默认等待时间是 1 秒,也就是说,OpenFeign 调用 service 后,只给 service 1秒钟的时间处理好,如果服务方处理了 2 秒才返回,OpenFeign 是不会等待的,直接报错。
OpenFeign 底层是使用 Ribbon 进行负载均衡,所以这个超时时间是由 ribbon 控制的,我们可以在配置文件这样配置:
ribbon:
ReadTimeout: 5000 # 建立连接最大超时时间
ConnectTimeout: 5000 # 连接后,从服务方读取可用资源的最大等待时间
Feign 提供了日志打印功能,可以通过配置来调整日志显示级别,从而了解 Feign 中 Http 请求的细节,说白了就是对 Feign 接口的调用情况进行监控和输出。
OpenFeign日志级别有:
1、NONE:默认的,不显示日志;
2、BASIC:仅记录请求方法、URL、响应状态码及执行时间;
3、HEADERS:除了 BASIC 中的信息外,还有请求和响应的头信息;
4、FULL:除了 HEADERS 中的信息之外,还有请求和响应的正文及元数据。
首先在消费方定义一个 Feign 的配置类,从中配置日志级别:
package pers.klb.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
接着在配置文件中配置对哪个接口进行什么样的监控监控:
logging:
level:
pers.klb.springcloud.service.PaymentFeignService: debug
这里配置的是对 PaymentFeignService
接口进行 debug 模式的监控。