本文最后更新于:2025年5月30日 下午
远程调用之OpenFeign
在单体项目中,正常写的接口都是供前端调用获取数据的(例如:ajax
、axios
和fetch
)。但是在微服务下的分布式架构中,不同的业务功能模块由不同人员负责并开发,因为分的比较细,所以就少不了服务之间的沟通。通常java
来进行远程调用的方法包括:RestTemplate
、HttpURLConnection
、HttpClient
、OkHttp
等。
而OpenFeign
就是简化微服务之间接口调用的远程调用工具之一。
前提已启动Nacos
注册中心!
一、Maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <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>
<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;
@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;
@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: com.demo.feign: debug
|
六、请求超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| feign: httpclient: enabled: false okhttp: enabled: true client: config: default: connectTimeout: 2000 readTimeout: 3000 loggerLevel: full test02: connectTimeout: 5000 readTimeout: 5000 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;
@Configuration public class DefaultFeignConfig {
@Bean public Logger.Level feignLogLevel(){ return Logger.Level.FULL; } @Bean Retryer retryer(){ 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;
@Component @Slf4j public class OpenFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { 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;
@Repository @FeignClient(value = "test02",fallback = Rest02FallBack.class)
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;
@Component public class Rest02FallBack implements com.demo.feign.Rest02 { @Override public String test002() { return "Default Data"; } }
|
添加Sentinel
依赖
1 2 3 4 5
| <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 请求体问题
其他情况:
情况 |
客户端 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 + 查询参数 |
关键点
- OpenFeign 的 GET 请求参数必须显式加
@RequestParam
,否则会被当成 Body 数据,强制用 POST。
- 对象参数要用
@SpringQueryMap
,避免默认的 @RequestBody
行为。
- 服务端 Controller 也要保持一致,避免混淆。