返回资源中心

SpringCloud OpenFeign 服务间调用 - 常规代码实现

把"用 Feign 做服务间调用"这件事讲透——Feign 是什么、为什么用它、@FeignClient 注解怎么写、参数怎么传、怎么注入调用。这是最基础的常规做法;下一课再讲企业里更进阶的 client-sdk 封装。小红学堂更新于 2026年8月29日209 次阅读

本课目标:把"用 Feign 做服务间调用"这件事讲透——Feign 是什么、为什么用它、@FeignClient 注解怎么写、参数怎么传、怎么注入调用。这是最基础的常规做法;下一课再讲企业里更进阶的 client-sdk 封装。


一、痛点:服务拆开后,它们怎么互相调用?

微服务把系统拆成一堆独立服务后,一个绕不开的现实问题是:服务之间经常要互相协作。

比如"下单"时,订单服务需要去问商品服务:"这个商品还有库存吗?"

在单体应用里,这就是一次普通的方法调用。但现在两个服务跑在不同的进程、不同的机器上,订单服务只能通过 HTTP 网络请求去调商品服务。

如果手写这个调用,你得做一堆事:

// 传统做法:手写 HTTP(伪代码),很啰嗦
String url = "http://product-service:8087/api/product/123";
HttpResponse resp = httpClient.get(url);          // 自己拼URL、发请求
String json = resp.getBody();                      // 拿到JSON字符串
ProductDTO product = objectMapper.readValue(json, ProductDTO.class);  // 自己反序列化

每调一个接口都要:拼 URL、发请求、处理响应、反序列化、处理异常…… 又啰嗦又容易错。有没有办法像调本地方法一样调远程服务?有,这就是 Feign


二、Feign 是什么

Feign 是一个"声明式"的 HTTP 客户端:你只要写一个接口、加几个注解,声明"我要调哪个服务的哪个接口",Feign 就在底层帮你把它变成一次 HTTP 请求。你调接口方法,就像调本地方法一样,完全不用自己写网络代码。

一句话:Feign = 让"调别的服务"看起来像"调本地方法"的翻译官。

📌 名词澄清:我们用的是 Spring Cloud 的 OpenFeign。"OpenFeign"是这个库的名字;真正写代码时用的注解是 @FeignClient(声明客户端)和 @EnableFeignClients(启用)。


三、三步接入 Feign

第一步:加依赖

在要发起调用的服务(消费方,比如订单服务)的 pom.xml 里加:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

第二步:启动类开启 Feign

@SpringBootApplication
@EnableFeignClients   // 启用 Feign:扫描并创建所有 @FeignClient 接口的代理
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

@EnableFeignClients 会扫描 @FeignClient 注解的接口,**自动为每个接口生成一个实现(动态代理)**并放进 Spring 容器,你直接注入就能用。

第三步:写一个 @FeignClient 接口

这是核心。声明"我要调商品服务的哪些接口":

@FeignClient(
    name = "product-service",                       // 服务名(标识,也用于服务发现)
    url  = "http://localhost:8087",                 // 目标地址(基础版写死;接Nacos可省略)
    path = "/api/product"                           // 该客户端所有接口的统一前缀
)
public interface ProductClient {

    /** 查询商品详情 —— 对应商品服务的 GET /api/product/{id} */
    @GetMapping("/{id}")
    ProductDTO getProduct(@PathVariable("id") Long id);

    /** 扣减库存 —— 对应商品服务的 POST /api/product/deduct-stock */
    @PostMapping("/deduct-stock")
    Boolean deductStock(@RequestBody DeductStockReq req);
}

就这么简单——没有一行网络代码,全靠注解声明。


四、@FeignClient 接口怎么写(详解)

4.1 @FeignClient 的几个关键属性

属性作用例子
name服务名(必填)。多个 client 调同一服务时还要配 contextId 区分"product-service"
url目标地址。基础版写死;接入 Nacos 后可不写,用服务名自动发现"http://localhost:8087"
path该接口下所有方法的统一路径前缀"/api/product"

4.2 方法 = 提供方的一个接口

写 Feign 接口的诀窍:照着"提供方的 Controller"抄。 提供方接口长什么样,Feign 方法就声明成什么样——路径、方法、参数一一对应

4.3 参数怎么传

Feign 用 Spring MVC 的注解来描述参数怎么放进 HTTP 请求:

注解参数放在哪用于
@PathVariableURL 路径里/product/{id} 这种
@RequestParamURL 问号后的查询参数?name=xxx&page=1
@RequestBody请求体(JSON)POST/PUT 提交对象
@RequestHeader请求头传 token、租户ID 等
// 各种参数写法示例
@GetMapping("/{id}")
ProductDTO getProduct(@PathVariable("id") Long id);                 // 路径参数

@GetMapping("/search")
List<ProductDTO> search(@RequestParam("name") String name,         // 查询参数
                        @RequestParam("page") Integer page);

@PostMapping("/create")
ProductDTO create(@RequestBody CreateProductReq req);              // 请求体

@GetMapping("/mine")
ProductDTO mine(@RequestHeader("X-User-Id") Long userId);          // 请求头

⚠️ 新手坑:Feign 接口里的 @GetMapping/@RequestParam 等注解,必须显式写出名字(如 @PathVariable("id")@RequestParam("name")),不能省,否则运行时可能报参数名解析失败。


五、像调本地方法一样使用

把 Feign 接口当成一个普通 Bean 注入,直接调用:

@Service
@RequiredArgsConstructor
public class OrderService {

    private final ProductClient productClient;   // 直接注入 Feign 接口

    public void createOrder(Long productId, int qty) {
        // 像调本地方法一样调商品服务!
        ProductDTO product = productClient.getProduct(productId);
        if (product.getStock() < qty) {
            throw new BusinessException("库存不足");
        }
        productClient.deductStock(new DeductStockReq(productId, qty));
        // ... 继续下单逻辑
    }
}

看,productClient.getProduct(productId) 读起来和调本地方法一模一样,但底层是一次跨服务的 HTTP 调用。这就是声明式调用的优雅之处。


六、调用全过程


七、Feign 怎么找到目标服务?——服务发现 + 负载均衡

先回到第三步那个写死的地址:url = "http://localhost:8087"。本地 demo 没问题,但生产环境根本行不通

  • 商品服务为了扛流量,往往部署了好几台(集群),到底该调哪一台?
  • 服务器重启、扩容、容器漂移后 IP 会变,写死的地址立刻失效。

所以生产里 @FeignClient 不写 url,只写服务名

@FeignClient(name = "product-service", path = "/api/product")   // 注意:没有 url 了
public interface ProductClient { ... }

那 Feign 凭一个"服务名"怎么找到真实 IP?靠 注册中心 + 负载均衡 两件事配合。

7.1 注册中心:服务的"通讯录"

每个服务启动时,把自己的 IP + 端口 登记到注册中心(如 Nacos);Feign 调用时,拿服务名去注册中心查出这个服务当前所有可用实例的地址列表

这就是上一课《微服务架构》里讲过的"服务注册与发现"——注册中心就像一个实时更新的通讯录。

7.2 负载均衡:从多个实例里挑一个

查到的往往是一串地址(商品服务有 3 台),到底发给哪台?这一步"从实例列表里挑一个"就是负载均衡,由 Spring Cloud LoadBalancer 完成(默认轮询)。

完整链路:Feign 拿服务名 → LoadBalancer 去 Nacos 查实例列表 → 挑一个实例 → 真正发 HTTP 请求。 整个过程业务代码完全无感,依然是 productClient.getProduct(123) 一行。

7.3 历史演进:Ribbon → Spring Cloud LoadBalancer

早期 Spring Cloud 的负载均衡用的是 Netflix Ribbon。后来 Ribbon 停止维护,Spring Cloud 用官方自研的 Spring Cloud LoadBalancer 取而代之。新项目一律用 Spring Cloud LoadBalancer,Ribbon 只在老项目里还能见到。

接入时依赖大致如下(了解即可,完整接入是注册中心课程的内容):


<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

本节记住一句:@FeignClient 写服务名而非 IP,靠"注册中心查地址 + LoadBalancer 挑实例"完成寻址和负载均衡——这才是生产里 Feign 的真实工作方式。


八、几个实用配置(够用就好)

# application.yml —— Feign 常用配置
feign:
  client:
    config:
      default:                  # default = 对所有 Feign 客户端生效
        connect-timeout: 2000   # 连接超时(ms)
        read-timeout: 5000      # 读取超时(ms)
  # 也可针对单个客户端名(如 product-service)单独配
logging:
  level:
    com.xiangmu.order.client: DEBUG   # 把 Feign 接口包设为 DEBUG 可打印请求/响应,方便排错

📌 提示:示例里的 url 是本地调试写法。生产中改用"服务名 + 服务发现 + 负载均衡"(见第七节),更完整的 Nacos 接入是后面注册中心课程的内容。


九、常规做法的局限(引出下一课)

到这里,"用 Feign 做服务间调用"你已经会了。但在多服务、多团队的真实企业项目里,这种"各调各的"做法会暴露几个问题:

问题说明
重复定义每个调商品服务的人,都自己写一遍 @FeignClient 接口和 DTO
DTO 容易对不上各自定义返回对象,商品服务改了字段,调用方未必同步
响应解包重复真实返回往往是 ApiResponse<T>,每个调用方都要自己解一层
降级容错重复商品服务挂了怎么办?每个调用方都得自己写 try-catch / 兜底

这些重复和不一致,正是下一课要解决的——把"怎么调我"由服务提供方统一封装成一个 client-sdk,别人依赖即用。那才是企业里的最佳实践。


十、小结

六句话带走:

  1. Feign 是声明式 HTTP 客户端:写接口 + 注解,像调本地方法一样调远程服务。
  2. 三步接入:加 openfeign 依赖 → 启动类 @EnableFeignClients → 写 @FeignClient 接口。
  3. 接口照着提供方 Controller 抄:路径、方法、参数一一对应,参数名要显式写。
  4. 注入即用:把 Feign 接口当普通 Bean 注入,直接调方法。
  5. 生产里靠服务名寻址@FeignClient 写服务名,靠注册中心查实例 + Spring Cloud LoadBalancer(早期 Ribbon)做负载均衡。
  6. 常规做法有重复和不一致问题 → 下一课用 client-sdk 封装解决。