四
服务调用:OpenFeign
Declarative HTTP Client with OpenFeign
服务之间要互相调接口。用 RestTemplate 吧,拼 URL、设 header、转 JSON,一堆样板代码。OpenFeign 的思路是:你只定义一个 Java 接口,加几个注解,剩下的 HTTP 调用 Feign 帮你搞定——就像调用本地方法一样调远程服务。
论OpenFeign 解决了什么痛点
没有 Feign 时:订单服务要调用户服务,得自己写 restTemplate.getForObject("http://user-service/user/" + id, User.class),URL 拼错了、超时没设、JSON 转错了,全是坑。
有 Feign 时:定义一个 UserClient 接口,方法上加 @GetMapping("/user/{id}"),注入到订单服务里直接 userClient.getUser(1L) 调用。Feign 在运行时给你生成实现类,自动走负载均衡、自动序列化。
三步接入 OpenFeign
第一步:订单服务的 pom.xml 加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:启动类加 @EnableFeignClients
@SpringBootApplication
@EnableFeignClients // 打开 Feign 开关,扫描 @FeignClient 接口
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
第三步:写一个 Feign 接口,声明要调的远程服务
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
// name = 要调用的服务名(Nacos 里注册的那个名字)
// fallback = 降级类,后面讲 Sentinel 时再用
@FeignClient(name = "user-service", path = "/user")
public interface UserClient {
// GET /user/{id} —— @PathVariable 对应路径里的 {id}
@GetMapping("/{id}")
UserDTO getUserById(@PathVariable("id") Long id);
// POST /user —— @RequestBody 把对象序列化成 JSON 发过去
@PostMapping
UserDTO createUser(@RequestBody UserDTO user);
// GET /user/search?keyword=xxx —— @RequestParam 对应 URL 参数
@GetMapping("/search")
List<UserDTO> search(@RequestParam("keyword") String keyword);
}
在订单服务里注入并使用,跟调本地方法一模一样
@RestController
@RequestMapping("/order")
public class OrderController {
// Feign 会自动生成实现类,直接注入
@Autowired
private UserClient userClient;
@GetMapping("/{orderId}")
public OrderDTO detail(@PathVariable Long orderId) {
OrderDTO order = orderService.getById(orderId);
// 远程调用户服务拿用户信息,写起来像本地调用
UserDTO user = userClient.getUserById(order.getUserId());
order.setUserName(user.getName());
return order;
}
}
超时配置与日志级别
Feign 默认超时很短(connect 2 秒、read 60 秒,具体以官方为准),调慢接口容易超时。日志级别默认 NONE,出问题时啥都看不到。
application.yml 里调超时和日志
feign:
client:
config:
# default 表示对所有 FeignClient 生效,也可以写具体服务名 user-service
default:
connect-timeout: 3000 # 连接超时 3 秒
read-timeout: 5000 # 读取超时 5 秒
logger-level: FULL # NONE/BASIC/HEADERS/FULL,调试用 FULL
Feign 参数绑定与 LoadBalancer 的关系
Feign 接口上的注解,跟 Spring MVC 控制器上的注解长得几乎一样——因为 Feign 就是把这套注解"反过来用":MVC 是接收请求,Feign 是构造请求。对应关系如下表,写 Feign 接口时直接照着 MVC 那套习惯来就行。
| 注解 | 作用 | 对应 HTTP 部分 |
@PathVariable("id") | 路径里的变量 | /user/{id} |
@RequestParam("kw") | URL 查询参数 | ?kw=xxx |
@RequestBody | 请求体 JSON | POST/PUT 的 body |
@RequestHeader("token") | 请求头 | Header 字段 |
Feign 调 lb://user-service 时,背后是 Spring Cloud LoadBalancer 在挑实例。老版本用 Ribbon,Ribbon 已停更,现在统一用 LoadBalancer。默认是轮询(round-robin),想换随机或权重,写个 ReactorLoadBalancer Bean 覆盖即可,学习阶段不用碰。
完整案例:Feign 接口 + fallback 降级类 + 超时日志
前面讲了怎么定义接口,这里把"超时、日志、降级"一次配齐。降级有两种写法:fallback(出问题直接调兜底类)和 fallbackFactory(能拿到异常原因,更灵活)。
第一步:写一个降级实现类,跟 Feign 接口同签名
// 这个类实现 UserClient 接口,每个方法都返回兜底数据
@Component
public class UserClientFallback implements UserClient {
@Override
public UserDTO getUserById(Long id) {
UserDTO dto = new UserDTO();
dto.setId(id);
dto.setName("用户服务暂时不可用(降级)");
return dto;
}
@Override
public UserDTO createUser(UserDTO user) {
return new UserDTO();
}
@Override
public List<UserDTO> search(String keyword) {
return Collections.emptyList();
}
}
第二步:@FeignClient 挂上 fallback
@FeignClient(name = "user-service",
path = "/user",
fallback = UserClientFallback.class) // 调用失败走这个类
public interface UserClient { ... }
第三步:application.yml 配超时 + 全量日志(调试用)
feign:
client:
config:
user-service: # 只对 user-service 这个客户端生效
connect-timeout: 2000
read-timeout: 5000
logger-level: FULL # 打印请求头/体/响应,线上别开
# 让 Feign 集成 Sentinel(熔断才能触发 fallback)
sentinel:
enabled: true
跑起来的输出对比
# 正常调用 user-service 在线:
GET /order/detail/1 → {"orderId":1, "userName":"张三", "product":"机械键盘"}
# 把 user-service 停掉,再调:
GET /order/detail/1 → {"orderId":1, "userName":"用户服务暂时不可用(降级)"}
# 全程不报错、不卡,前端拿到的是兜底文案 —— 这就是降级
Feign 超时默认 1 秒是历史遗留坑
老版本 Spring Cloud(Hoxton 之前)Feign 默认 read timeout 是 1 秒,调用稍微慢一点就 ReadTimeoutException。新版本默认值放宽了,但只要你调的是数据库慢查询、第三方接口,一定要显式配超时。另外,Feign 的超时会被 Ribbon/LoadBalancer 的超时覆盖——现在统一用 Spring Cloud LoadBalancer,配置项别找错地方。
本章面试题
面试快答 · OpenFeign
Q1. Feign 和 RestTemplate 比,好在哪?
参考答案
Feign 是声明式:定义接口 + 注解,运行时动态生成实现,自动序列化、自动负载均衡、自带超时/日志/降级配置;RestTemplate 要手写拼 URL、设 header、转 JSON。本质是把"远程调用"伪装成"本地方法调用"。
Q2. fallback 和 fallbackFactory 区别?
参考答案
fallback 只给一个兜底结果,拿不到异常原因;fallbackFactory 能拿到 Throwable,知道是超时、限流还是业务异常,可以区别处理(比如超时返回"稍后重试"、熔断返回"维护中")。
Q3. Feign 是同步还是异步?底层用什么?
参考答案
默认同步阻塞,底层默认 HttpURLConnection,可换 Apache HttpClient/OkHttp。要响应式流式,用 WebClient。别和 Gateway 的 WebFlux 混——Feign 走 Tomcat 线程模型。