目录儿
七、OpenFeign远程调用
7.1 简介
前面说的Resttemplate+Ribbon的方式调用服务每次都需要填写远程地址和配置各种参数,非常麻烦。
OpenFeign远程调用框架相比而言更加的简洁好用。
在OpenFeign之前有一个Feign框架,OpenFeign算是它的增强版,进一步封装支持了Spring MVC的标准注解和HttpMessage-Converters,如@RequestMapping等。
直接复制一份
Sentinel那个项目,改下名字作为这次的试验项目
7.2 集成OpenFeign
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 修改
bootstrap.yml配置文件
feign:
sentinel:
enabled: true # 开启 openfeign 对 sentinel 的限流支持
- 创建一个接口作为远程调用接口的本地媒介
// value指定需要远程调用的服务的名称
@FeignClient(value = "sentinel-provider", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
/**
* 这个方法的信息就是来自要远程调用的那个接口方法,需要指定它的接口路径和类型
*/
@RequestMapping(value = "/providerTest", method = RequestMethod.GET)
public String providerTest();
}
- 创建接口对应兜底实现
@Component
public class OpenFeignTestFallback implements OpenFeignTestService {
public String providerTest() {
return "return from OpenFeignTest 兜底方法";
}
}
- 在启动类中贴上
@EnableFeignClients注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 开启 Feign 服务
public class Concumer8041 {
public static void main(String[] args) {
SpringApplication.run(Concumer8041.class, args);
System.out.println("-------------启动成功------------");
}
}
- 创建一个测试接口
@RestController
@RefreshScope // 配置自动更新
public class TestController {
@Resource
private OpenFeignTestService openFeignTestService;
@RequestMapping("/openFeignTest")
public String openFeignTest() {
return openFeignTestService.providerTest(); // 以本地接口的方式,实际上调用的是远程接口
}
...
}
启动消费者服务时出现问题:
java.lang.IllegalAccessError: class org.springframework.cloud.openfeign.HystrixTargeter$$EnhancerBySpringCGLIB$$a79797c cannot access its superclass org.springframework.cloud.openfeign.HystrixTargeter
解决:去掉spring-boot-devtools热部署依赖
参考:https://blog.csdn.net/moakun/article/details/113764393
- 远程调用接口(服务提供)
@RestController
public class TestController {
@Value("${server.port}")
private String port;
@RequestMapping("/providerTest")
public String providerTest() {
return "return from service provider test,服务端口:" + port;
}
}
- 测试
调用localhost:8041/openFeignTest接口,它又去调用了远程接口,成功拿到了返回结果
接下来在/providerTest接口(被远程调用的接口)制造人为异常
@RequestMapping("/providerTest")
public String providerTest() {
int i = 1 / 0;
return "return from service provider test,服务端口:" + port;
}
再次调用localhost:8041/openFeignTest接口,这时候它去调用远程接口的时候出现异常了,然后会调用fallback属性指定的兜底方法做响应。
7.3 代码优化
在我们创建本地接口去对应远程被调接口的时候,通过@FeignClient注解的value属性指定了服务的名称,但是这样就写死了,后续如果那个被调服务更改了名字,那么每个通过@FeignClient注解调用它的接口都需要更改,不方便。
// value指定需要远程调用的服务的名称
@FeignClient(value = "sentinel-provider", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
/**
* 这个方法的信息就是来自要远程调用的那个接口方法,需要指定它的接口路径和类型
*/
@RequestMapping(value = "/providerTest", method = RequestMethod.GET)
public String providerTest();
}
两个方案
- 用静态变量
- 用配置文件
一般来说用配置文件会比较合适。
更改nacos上的配置
provider:
name: service-provider
从配置获取
@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)
7.4 结合Sentinel使用
openfeign是可以和sentinel结合使用的。
通常是给被调用的接口添加流控,因此这里就给/providerTest接口添加流控规则
在达到限流条件的时候,会进入fallback指定的兜底类执行兜底方法。
@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
/**
* 这个方法就是来自要远程调用的那个接口方法的信息,需要指定它的接口路径和类型
*/
@RequestMapping(value = "/providerTest", method = RequestMethod.GET)
public String providerTest();
}
只要是被调用的接口没有调用成功,都会进入兜底方法。
这就是比较简单的结合用法。
7.5 负载均衡
复制一份服务供应模块,配置好nacos即可,通过服务名调用接口默认实现轮询。
参考:https://blog.csdn.net/qq_31856061/article/details/127090837
7.6 超时配置
openfeign默认等待超时为1s,超过一秒将进入fallback函数响应
provider接口睡眠2s模拟业务处理,访问
修改配置文件配置请求超时时间
feign:
sentinel:
enabled: true # 开启 openfeign 对 sentinel 的限流支持
client: # 新增
config: # 新增
default: # 新增
connectTimeout: 5000 # 新增 连接超时时间 单位毫秒
readTimeout: 5000 # 新增 建立连接后从服务器读取资源超时时间 单位毫秒
设置好后就可以成功调用那些响应慢的接口而不会进入兜底方法
7.7 OpenFeign远程调用日志
openfeign默认不输出日志到控制台,可以通过配置开启日志
- 配置日志等级
logging: # 新增
level: # 新增
com.echoo.cloud.openfeign.consumer.service.OpenFeignTestService: debug # 新增

- 添加一个配置类
@Configuration
public class OpenFeignLoggerConfiguration {
@Bean
public Logger.Level openFeignLoggerLevel() {
return Logger.Level.FULL; // FULL 日志级别
}
}
OpenFeign有四种日志级别:
NONE无记录(默认)
BASIC只记录请求方法、URL、响应状态码和执行时间四个基本信息
HEADERS只记录基本信息及请求和响应头
FULL记录所有信息
如此设置后,远程调用日志就会打印在控制台
7.8 压缩请求和响应
当请求或响应传输的数据量比较大,可以开启openfeign的压缩功能对请求和响应进行压缩,提高传输效率
配置:
feign:
sentinel:
enabled: true # 开启 openfeign 对 sentinel 的限流支持
client:
config:
default:
connectTimeout: 5000 # 连接超时时间 单位毫秒
readTimeout: 5000 # 建立连接后从服务器读取资源超时时间 单位毫秒
compression: # 新增 压缩
request: # 新增 请求设置
enabled: true # 新增 允许压缩请求
mime-types: text/xml,application/xml,application/json # 新增 需要压缩的请求类型
min-request-size: 2048 # 新增 最小请求长度 单位字节
response: # 新增 响应
enabled: true # 新增 允许压缩响应
日志中可以看到用了gzip压缩
7.9 接口传递参数
7.9.1 传递简单参数
首先在服务提供者模块中写一个新的接口,包含了两个简单参数,一个String类型,一个Integer类型
@PostMapping("/simpleParamsTest")
public String simpleParamsTest(@RequestParam("name") String name, @RequestParam("num") Integer num) {
return String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[%s,%d]", port, name, num);
}
在消费者中对应的接口中增加一个对应的方法
@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
@RequestMapping(value = "/providerTest", method = RequestMethod.GET)
public String providerTest();
/** 新增的接口方法 */
@PostMapping("/simpleParamsTest")
public String simpleParamsTest(@RequestParam("name") String name, @RequestParam("num") Integer num);
}
然后再实现对应的兜底方法
@Component
public class OpenFeignTestFallback implements OpenFeignTestService {
public String providerTest() {
return "return from OpenFeignTest 兜底方法";
}
/** 对应的兜底方法 */
public String simpleParamsTest(String name, Integer num) {
return "return from simpleParamsTest 兜底方法";
}
}
最后在消费者中添加一个接口,用来远程调用测试接口,记得调用远程接口时候传参
@PostMapping("/simpleParamsTest")
public String simpleParamsTest() {
return openFeignTestService.simpleParamsTest("mary", 66);
}
访问测试没毛病,参数传递没问题
7.9.2 GET请求传递对象
OpenFeign使用@SpringQueryMap注解让Get请求也能简单的传递对象。
- 创建传递对象
因为服务消费者和服务提供者都要用到这个对象,因此把该类放到公共模块commom中,因为我这儿还没公共模块,需要新建一个commom模块
- 创建数据对象
@Getter
@Setter
public class Params {
private Integer num;
private String name;
}
- 在服务提供者模块添加一个测试接口
@GetMapping("/springQueryMapProviderTest")
public String springQueryMapProviderTest(Params params) {
return String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[name=%s,num=%d]", port, params.getName(), params.getNum());
}
- 在服务消费者模块添加对应的接口和实现对应的兜底方法
@GetMapping("/springQueryMapProviderTest")
public String springQueryMapProviderTest(@SpringQueryMap Params params);
public String springQueryMapProviderTest(Params params) {
return "return from springQueryMapProviderTest 兜底方法";
}
@SpringQueryMap会自动把路径对应的参数封装到对象中
- 在服务消费者模块中添加一个测试接口
@GetMapping("/springQueryMapTest")
public String springQueryMapTest() {
Params params = new Params();
params.setName("Mike");
params.setNum(788);
return openFeignTestService.springQueryMapProviderTest(params);
}
- 访问测试接口,没毛病,对象正确传递

7.9.3 传递复杂对象
- 创建复杂对象
对象中包含对象
@Getter
@Setter
public class ComplexObject {
private Params params;
}
- 创建返回值对象
用于封装返回信息
@Setter
@Getter
public class Result {
private Integer code;
private String description;
}
- 在服务提供者模块添加接口
@PostMapping("/simpleParamsTest")
public Result complexObjectProviderTest(@RequestBody ComplexObject object) {
Result result = new Result();
result.setCode(200);
result.setDescription(String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[%s,%d]", port, object.getParams().getName(), object.getParams().getNum()));
return result;
}
接口接收对象记得用
@RequestBody,单个参数用@RequestParam
- 在服务消费者模块添加对应的接口方法和兜底方法
@PostMapping("/complexObjectProviderTest")
public Result complexObjectProviderTest(@RequestBody ComplexObject object);
public Result complexObjectProviderTest(ComplexObject object) {
Result result = new Result();
result.setCode(500);
result.setDescription("return from complexObjectProviderTest 兜底方法");
return result;
}
- 在服务消费者模块添加测试接口
@PostMapping("/complexObjectTest")
public Result complexObjectTest() {
Params params = new Params();
params.setName("Julia");
params.setNum(5959);
ComplexObject complexObject = new ComplexObject();
complexObject.setParams(params);
return openFeignTestService.complexObjectProviderTest(complexObject);
}
- 访问测试接口,复杂对象正确传递
