远程调用之OpenFeign

本文最后更新于:2025年5月30日 下午

远程调用之OpenFeign

在单体项目中,正常写的接口都是供前端调用获取数据的(例如:ajaxaxiosfetch)。但是在微服务下的分布式架构中,不同的业务功能模块由不同人员负责并开发,因为分的比较细,所以就少不了服务之间的沟通。通常java来进行远程调用的方法包括:RestTemplateHttpURLConnectionHttpClientOkHttp等。

OpenFeign就是简化微服务之间接口调用的远程调用工具之一。

前提已启动Nacos注册中心!

一、Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- OpenFeign 远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

二、启用OpenFeign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

// 开启OpenFeign
@EnableFeignClients
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

三、编写OpenFeign客户端

这一步类似Mapper文件,只不过数据通过远程调用从其他模块获取。而不是单体中由xml文件写SQL语句直接沟通数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service")
public interface ItemClient {


@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

四、【可选】OpenFeign日志配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.demo.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* OpenFeign远程调用配置类
* @author peter
* @date 2025/2/20
* @description TODO
*/
@Configuration
public class DefaultFeignConfig {

// 日志配置: 显示所有级别的日志
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}

五、【可选】启用日志配置

1、局部生效(客户端中指定配置)

1
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)

2、全局生效(配置类中指定默认配置)

1
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

3、yaml开启OpenFeign客户端的日志

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

1
2
3
4
5
logging:
level:
#root: debug
com.demo.feign: debug
#com.demo.interceptor: debug

六、请求超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 远程调用 > OpenFeign
feign:
httpclient:
enabled: false
okhttp:
enabled: true
client:
config:
default: # 默认设置,未具体设置的所有客户端都生效
connectTimeout: 2000 # 连接超时 5秒(5000毫秒) 【默认60秒】
readTimeout: 3000 # 读取超时 5秒
loggerLevel: full # 日志级别 所有
test02: # 服务名称[或是contextId指定的服务]
connectTimeout: 5000 # 连接超时 5秒(5000毫秒) 【默认60秒】
readTimeout: 5000 # 读取超时 5秒
loggerLevel: full # 日志级别 所有

七、超时重试

超时重试机制:超时时间(例如5秒)+ 重试间隔(100毫秒)x 1.5

每次重试的间隔都会比上次长,例如是1.5倍

最大到重试间隔的时间超过最大时间或者重试次数达到最大次数则停止重试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.demo.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* OpenFeign远程调用配置类
* @author peter
* @date 2025/2/20
* @description TODO
*/
@Configuration
public class DefaultFeignConfig {

// 日志配置: 显示所有级别的日志
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}

// 超时重试
@Bean
Retryer retryer(){
// 不传值走无参构造:默认间隔100毫秒,最大间隔1秒,最多尝试5次
//return new Retryer.Default();
// 传值走有参构造: 每次重试间隔100毫秒,最大间隔1秒,最多尝试5次
return new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
}

八、请求拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.demo.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
* OpenFeign请求拦截器
* @author peter
* @date 2025/2/20
* @description 用于请求前统一添加公共数据
*/
@Component
@Slf4j
public class OpenFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// 请求头添加X-Token
requestTemplate.header("X-Token",UUID.randomUUID().toString());
}
}

九、FallBack兜底返回

远程调用超时或错误时返回的错误信息或兜底数据。

兜底数据:默认数据、缓存数据、假数据… 主要是为了让业务能正常运行。

注意:需搭配Sentinel服务保护框架,否则不会走兜底的。

客户端:需要fallback指定兜底实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.demo.feign;

import com.demo.feign.fallback.Rest02FallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.GetMapping;

/**
* OpenFeign客户端接口请求
* @author peter
* @date 2025/2/19
* @className test02
* @packageName com.demo.feign
* @description 一个客户端中对应同一业务的所有请求
* @see
*/
@Repository
@FeignClient(value = "test02",fallback = Rest02FallBack.class)// 这里的值是微服务的名称
//@FeignClient(value = "test01",url = "https://www.baidu.com") // 若是调用第三方的api,则需指定地址
public interface Rest02 {

@GetMapping("index")
String test002();
}

兜底类:实现客户端接口然后在实现方法中增加兜底数据逻辑即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.demo.feign.fallback;

import org.springframework.stereotype.Component;

/**
* FallBack
*
* @author peter
* @date 2025/2/20
* @description TODO
*/
@Component
public class Rest02FallBack implements com.demo.feign.Rest02 {
@Override
public String test002() {
return "Default Data";
}
}

添加Sentinel依赖

1
2
3
4
5
<!-- sentinel 服务保护 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置文件中开启sentinel

1
2
3
feign:
sentinel:
enabled: true

这样把调用的服务停止,然后去调用就会获得兜底数据了。

测试

1
2
3
4
5
6
7
8
9
10
11
12
```

## 注意事项

### 传参问题

1. **OpenFeign 默认强制使用 POST**

- **即使你声明了 `@GetMapping`,OpenFeign 在某些情况下仍然会强制使用 POST**(特别是请求体非空时)。

- 示例:

Java@FeignClient(name = “service-name”)
public interface MyFeignClient {
@GetMapping(“/api/data”) // 声明 GET
String fetchData(@RequestBody RequestObj request); // 但用了 @RequestBody → 实际发 POST
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

问题:`@RequestBody`会让` OpenFeign `强制用` POST`,即使方法标注`@GetMapping`。

### 2. **Spring Boot 自动匹配错误**

- 如果 **Controller 方法没有明确指定 `method = RequestMethod.GET`**,Spring 可能会错误匹配 POST 请求。

- 示例:

```Java
@RestController
public class MyController {
@RequestMapping("/api/data") // 没有指定 GET/POST
public String getData() {
return "data";
}
}

问题:@RequestMapping默认支持GET、POST、PUT 等,如果 OpenFeign 误发 POST,Spring 仍然会处理,但可能不是你想要的。

3. OpenFeign 请求体问题

  • 即使没有 @RequestBody,如果参数是复杂对象(非 Stringint 等简单类型),OpenFeign 可能默认用 POST

  • 示例:

    1
    2
    3
    4
    5
    @FeignClient(name = "service-name")
    public interface MyFeignClient {
    @GetMapping("/api/data") // 声明 GET
    String fetchData(QueryParams params); // 参数是对象 → OpenFeign 可能用 POST
    }

其他情况:

情况 客户端 OpenFeign 服务端 Spring MVC 结果
不加注解 fetchData(Long id) getData(Long id) ❌ OpenFeign 默认用 POST
@RequestParam fetchData(@RequestParam Long id) getData(@RequestParam Long id) ✅ GET + 查询参数
对象参数 fetchData(@SpringQueryMap FilterParams params) getData(FilterParams params) ✅ GET + 查询参数

关键点

  1. OpenFeign 的 GET 请求参数必须显式加 @RequestParam,否则会被当成 Body 数据,强制用 POST。
  2. 对象参数要用 @SpringQueryMap,避免默认的 @RequestBody 行为。
  3. 服务端 Controller 也要保持一致,避免混淆。

远程调用之OpenFeign
https://superlovelace.top/2025/02/20/远程调用之OpenFeign/
作者
棱境
发布于
2025年2月20日
更新于
2025年5月30日
许可协议