一、前言
接下来是开展一系列的 SpringCloud 的学习之旅,从传统的模块之间调用,一步步的升级为 SpringCloud 模块之间的调用,此篇文章为第四篇,即介绍 Feign 和 OpenFeign 服务接口调用。
二、概述
2.1 Feign 是什么
Feign 是一个声明式的 Web 服务客户端,让编写 Web 服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
2.2 Feign 用途
2.2.1 Feign 能干什么
Feign 旨在使编写 Java Http 客户端变得更容易。前面在使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 http 请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
所以,Feign 在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在 Feign 的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是 Dao 接口上面标注 Mapper 注解,现在是一个微服务接口上面标注一个 Feign 注解即可),即可完成对服务提供方的接口绑定,简化了使用 Spring cloud Ribbon 时,自动封装服务调用客户端的开发量。
2.2.2 Feign 集成了 Ribbon
Feign 集成了 Ribbon 利用 Ribbon 维护了 Payment 的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与 Ribbon 不同的是,通过 feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
2.3 Feign 和 OpenFeign 区别
Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。
Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。需要引入的依赖如下:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequesMapping 等等。OpenFeign 的 @FeignClient 可以解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。需要引入的依赖如下:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
三、OpenFeign 使用步骤
3.1 新建工程
新建一个子模块工程 cloud-consumer-feign-order80,用于展示 OpenFeign 的使用步骤。
3.2 添加依赖
在 pom.xml 中,添加如下的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.springcloud</groupId><artifactId>SpringCloud</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloud-consumer-feign-order80</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--eureka client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><dependency><groupId>com.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--一般基础通用配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
</project>
3.3 添加配置文件
在 application.yml 中添加如下的配置信息:
server:port: 80eureka:client:register-with-eureka: falseservice-url:defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
3.3 修改启动类
需要在启动类上添加 @EnableFeignClients 注解用于开启 OpenFeign 的使用,代码如下:
package com.springcloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
// 开启 OpenFeign
@EnableFeignClients
public class OrderFeignMain80
{public static void main(String[] args){SpringApplication.run(OrderFeignMain80.class,args);}
}
3.4 创建业务类
创建业务逻辑接口 PaymentFeignService,并在接口上添加 @FeignClient 注解,代码如下所示:
package com.springcloud.service;import com.springcloud.entities.CommonResult;
import com.springcloud.entities.Payment;
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;@Component
// value 值是对外暴露的服务名称
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {@GetMapping(value = "/payment/get/{id}")CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);}
创建控制层代码 OrderFeignController,用于对外提供服务,代码如下:
package com.springcloud.controller;import com.springcloud.entities.CommonResult;
import com.springcloud.entities.Payment;
import com.springcloud.service.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
public class OrderFeignController {@Resourceprivate PaymentFeignService paymentFeignService;@GetMapping(value = "/consumer/payment/get/{id}")public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){return paymentFeignService.getPaymentById(id);}
}
3.5 测试
分别启动 cloud-eureka-server7001、cloud-eureka-server7002、cloud-provider-payment8001、cloud-provider-payment8002 和 cloud-consumer-feign-order80 模块,然后在浏览器输入 http://localhost/consumer/payment/get/1,进行测试,不断刷新界面如下图,可以看到,OpenFeign 还默认实现了负载均衡。
3.6 小结
四、OpenFeign 超时控制
4.1 简介
默认 Feign 客户端只等待一秒钟,但是服务端处理需要超过 1 秒钟,导致 Feign 客户端不想等待了,直接返回报错。
4.2 现象演示
在 cloud-provider-payment8001 模块的 PaymentController 中添加一个测试方法,如下:
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeOut()
{System.out.println("*****paymentFeignTimeOut from port: "+serverPort);//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }return serverPort;
}
在 cloud-consumer-feign-order80 模块的 PaymentFeignService 中添加一个调用方法,代码如下:
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {@GetMapping(value = "/payment/get/{id}")CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);@GetMapping(value = "/payment/feign/timeout")String paymentFeignTimeOut();}
在 cloud-consumer-feign-order80 模块的 OrderFeignController 中添加一个调用方法,代码如下:
@RestController
public class OrderFeignController {@Resourceprivate PaymentFeignService paymentFeignService;@GetMapping(value = "/consumer/payment/get/{id}")public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){return paymentFeignService.getPaymentById(id);}@GetMapping(value = "/consumer/payment/feign/timeout")public String paymentFeignTimeOut(){return paymentFeignService.paymentFeignTimeOut();}
}
分别启动 cloud-eureka-server7001、cloud-eureka-server7002、cloud-provider-payment8001 和 cloud-consumer-feign-order80 服务,然后在浏览器输入 http://localhost/consumer/payment/feign/timeout,进行测试,如下图,可以看到出现了超时的错误。
4.3 问题解决
由于 OpenFeign 默认支持 Ribbon,所以只需要在 cloud-consumer-feign-order80 模块的 application.yml 中开启并配置 OpenFeign 的客户端超时控制即可,如下:
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间ReadTimeout: 5000#指的是建立连接后从服务器读取到可用资源所用的时间ConnectTimeout: 5000
重启 cloud-consumer-feign-order80 模块,重新调用测试的 url 如下图:
五、OpenFeign 日志打印功能
5.1 简介
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对 Feign 接口的调用情况进行监控和输出。
5.2 日志级别
1、NONE:默认的,不显示任何日志;
2、BASIC:仅记录请求方法、URL、响应状态码及执行时间;
3、HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
4、FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
5.3 配置
在 cloud-consumer-feign-order80 模块中新建一个配置类 FeignConfig ,代码如下:
package com.springcloud.config;import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignConfig {@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}
在 application.yml 中配置日志的 Feign 客户端,因为 spring 的默认日志级别是 info,而 openFegin 打印日志需要 debug,所以需要将 spring 日志级别改为 debug,在 application.yml 中,为了只打印 openFegin 日志,可以只将 openFegin 接口的包路径设置为 dubug。
logging:level:# feign日志以什么级别监控哪个接口com.springcloud.service.PaymentFeignService: debug
5.4 测试
随便调用一个测试方法,后台输出的日志如下: