乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 微服务与 Spring Cloud 概述

    • 微服务与 Spring Cloud 概述
  • 第2章 服务注册与发现

    • Eureka 服务注册与发现
    • Consul 服务注册与发现
  • 第3章 客户端负载均衡

    • Ribbon 客户端负载均衡
    • Spring Cloud LoadBalancer
  • 第4章 声明式服务调用

    • Feign 声明式 HTTP 客户端
    • OpenFeign 高级特性与 Spring Cloud 集成
  • 第5章 服务容错与熔断

    • Resilience4j 服务容错与熔断
    • Sentinel 流量控制与熔断降级
  • 第6章 配置中心

    • Spring Cloud Config 配置中心
  • 第7章 API 网关

    • Spring Cloud Gateway 现代化 API 网关
    • Zuul 网关(已进入维护模式)
  • 第8章 消息驱动的微服务

    • Spring Cloud Stream 消息驱动的微服务
  • 第9章 分布式追踪与监控

    • Sleuth + Zipkin 分布式链路追踪
  • 第10章 最佳实践与面试考点

    • Spring Cloud 最佳实践与面试考点

Feign 声明式 HTTP 客户端

导学

小崔刚写完一段调用库存服务的代码,白歌 review 时皱了皱眉:

// 小崔写的 RestTemplate 调用代码
String url = "http://FEIXIANG-INVENTORY-SERVICE/inventory/" + productId;
Map<String, Object> result = restTemplate.getForObject(url, Map.class);

"这段代码有什么问题?"白歌问。

小崔想了想:"URL 拼接容易出错,返回 Map 没有类型安全,而且如果我要传 JSON 请求体,还得写一堆 HttpEntity 构造代码..."

"这就是 Feign 要解决的问题——把 HTTP 调用写得像本地方法调用一样。"


定位与问题场景

在微服务中,消费者调用提供者本质上是一次 HTTP 请求。但直接使用 RestTemplate 的痛点:

痛点RestTemplate 方案Feign 方案
URL 拼接手动字符串拼接注解声明
参数绑定手动 URI 变量@PathVariable / @RequestParam
请求体HttpEntity 包装@RequestBody
返回类型Map / String 无类型安全POJO 自动反序列化
代码量5-10 行/接口1 个接口定义

Feign 的核心设计理念:声明式 HTTP 客户端——只需要定义一个接口并加注解,Feign 在运行时动态生成实现类。

操作前后对比:

// 引入 Feign 前:RestTemplate
public Map checkStock(Long productId) {
    String url = "http://FEIXIANG-INVENTORY-SERVICE/inventory/" + productId;
    return restTemplate.getForObject(url, Map.class);
}

// 引入 Feign 后:声明式接口
@FeignClient("FEIXIANG-INVENTORY-SERVICE")
public interface InventoryClient {
    @GetMapping("/inventory/{productId}")
    StockDTO getStock(@PathVariable Long productId);
}

Feign 核心原理

动态代理机制

核心组件

组件职责
Contract解析 @RequestMapping / @FeignClient 等注解,生成方法元数据
Encoder将方法参数编码为 HTTP 请求体(默认 Jackson)
Decoder将 HTTP 响应体解码为返回类型(默认 Jackson)
Client真正发起 HTTP 请求的组件(默认 JDK HttpURLConnection)
LoggerFeign 日志,记录请求/响应详情
Interceptor请求拦截器,可添加统一请求头

完整示例:飞翔科技订单服务通过 Feign 调用

示例一:基础调用(查询库存)

库存服务提供者:

@RestController
public class InventoryController {
    @GetMapping("/inventory/{productId}")
    public StockDTO getStock(@PathVariable Long productId) {
        return StockDTO.builder()
                .productId(productId)
                .productName("飞翔机械键盘 Pro")
                .stock(100)
                .build();
    }
}

订单服务消费者——Feign 接口:

@FeignClient(name = "FEIXIANG-INVENTORY-SERVICE")
public interface InventoryClient {
    @GetMapping("/inventory/{productId}")
    StockDTO getStock(@PathVariable("productId") Long productId);
}

订单服务消费端调用:

@RestController
public class OrderController {
    @Autowired
    private InventoryClient inventoryClient;

    @GetMapping("/order/check-stock/{productId}")
    public StockDTO checkStock(@PathVariable Long productId) {
        // 像调用本地方法一样调用远程服务
        return inventoryClient.getStock(productId);
    }
}

示例二:POST 请求 + 请求体(创建订单)

@FeignClient(name = "FEIXIANG-ORDER-SERVICE")
public interface OrderClient {
    @PostMapping("/orders")
    OrderDTO createOrder(@RequestBody OrderRequest request);

    @GetMapping("/orders/{orderId}")
    OrderDTO getOrder(@PathVariable("orderId") String orderId);
}

示例三:多参数 + 请求头

@FeignClient(name = "FEIXIANG-USER-SERVICE")
public interface UserClient {
    @GetMapping("/users/{userId}/orders")
    List<OrderDTO> getUserOrders(
        @PathVariable("userId") Long userId,
        @RequestParam("status") String status,
        @RequestHeader("X-Trace-Id") String traceId
    );
}

启动类配置

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.feixiang.order.client")
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

Feign 日志配置

开发阶段建议开启 FULL 级别日志,便于调试:

logging:
  level:
    com.feixiang.order.client.InventoryClient: DEBUG

feign:
  client:
    config:
      default:
        loggerLevel: FULL    # NONE/BASIC/HEADERS/FULL

FULL 级别日志输出示例:

[InventoryClient#getStock] ---> GET http://FEIXIANG-INVENTORY-SERVICE/inventory/1001
[InventoryClient#getStock] ---> END HTTP (0-byte body)
[InventoryClient#getStock] <--- HTTP/1.1 200 (145ms)
[InventoryClient#getStock] {"productId":1001,"productName":"飞翔机械键盘 Pro","stock":100}
[InventoryClient#getStock] <--- END HTTP (86-byte body)

易错场景

1. @PathVariable 未指定 value 导致启动失败

// ❌ 错误:缺少 value 属性
@GetMapping("/inventory/{productId}")
StockDTO getStock(@PathVariable Long productId);

// ✅ 正确:显式指定 value
@GetMapping("/inventory/{productId}")
StockDTO getStock(@PathVariable("productId") Long productId);

原因:Java 8 编译时默认不保留方法参数名,Feign 无法推断 @PathVariable 对应的 URL 变量名。

2. Feign 接口返回类型不能是基本类型

// ❌ 错误
@GetMapping("/inventory/{productId}")
int getStock(@PathVariable Long productId);

// ✅ 正确:使用包装类型
@GetMapping("/inventory/{productId}")
Integer getStock(@PathVariable Long productId);

3. 多个 @RequestBody 参数

// ❌ 错误:Feign 不支持多个 @RequestBody
@PostMapping("/orders")
OrderDTO createOrder(@RequestBody UserDTO user, @RequestBody ProductDTO product);

// ✅ 正确:合并为一个 DTO
@PostMapping("/orders")
OrderDTO createOrder(@RequestBody OrderRequest request);

4. 继承接口导致路径重复

// ❌ 错误:如果父接口也有 @RequestMapping,路径可能重复
@FeignClient("FEIXIANG-INVENTORY-SERVICE")
public interface InventoryClient extends BaseClient { ... }

// ✅ 正确:Feign 接口不要继承带 @RequestMapping 的接口

面试考点

Feign 的工作原理?

Feign 通过 JDK 动态代理,在运行时为 @FeignClient 标注的接口生成实现类。当调用接口方法时,代理对象通过 Contract 解析注解生成请求模板,Encoder 将参数编码为 HTTP 请求,HTTP Client 发起真实请求,Decoder 将响应解码为返回类型。整个过程对调用方透明。

Feign 支持哪些 HTTP 客户端?

默认使用 java.net.HttpURLConnection(无连接池,性能差)。生产环境推荐替换为:

  • Apache HttpClient:成熟稳定,连接池支持
  • OkHttp:轻量高性能,支持 HTTP/2

切换方法:引入对应 Starter 或手动配置 feign.httpclient.enabled=true / feign.okhttp.enabled=true

Feign 如何与 LoadBalancer 集成?

Feign 中配置的服务名(如 FEIXIANG-INVENTORY-SERVICE)会自动通过 LoadBalancer 解析为实际 IP:Port。如果项目中同时有 LoadBalancer 和 Feign,@FeignClient 的 name 就是服务名,Feign 内置的 LoadBalancer 支持会自动替换 URL。


小结

Feign 让微服务间的 HTTP 调用变得像调用本地方法一样简单。通过声明式注解、动态代理、编解码器,Feign 消除了模板代码。但它只是让调用写得更优雅,并没有解决调用失败的问题——下一节 OpenFeign 将结合熔断器,在声明式调用的基础上加入容错能力。

下一页
OpenFeign 高级特性与 Spring Cloud 集成