Skip to content

OpenFeign 声明式服务调用

5.1 OpenFeign 是什么?

OpenFeign 是声明式 HTTP 客户端。

它让我们像调用本地 Java 接口一样调用远程服务。

不用自己手写:

java
RestTemplate
WebClient
HttpClient
OkHttp

而是声明接口:

java
@FeignClient("stock-service")
public interface StockClient {

    @PostMapping("/stock/reduce")
    String reduce(@RequestParam("productId") Long productId,
                  @RequestParam("count") Integer count);
}

然后直接注入使用:

java
stockClient.reduce(1001L, 2);

5.2 OpenFeign 调用原理

核心流程:

text
启动时扫描 @FeignClient

为接口生成代理对象

调用接口方法

根据注解拼接 HTTP 请求

通过服务名找注册中心实例

负载均衡选择一个服务实例

发送 HTTP 请求

解析响应结果

5.3 OpenFeign 依赖

order-service 中引入:

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

还需要服务发现:

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

5.4 开启 Feign

order-service 启动类上添加:

java
package com.demo.order;

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

@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

5.5 编写 Feign 接口

java
package com.demo.order.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(name = "stock-service")
public interface StockClient {

    @GetMapping("/stock/info")
    String info();

    @PostMapping("/stock/reduce")
    String reduce(@RequestParam("productId") Long productId,
                  @RequestParam("count") Integer count);
}

5.6 order-service 调用 stock-service

java
package com.demo.order.controller;

import com.demo.order.client.StockClient;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/order")
public class OrderController {

    private final StockClient stockClient;

    public OrderController(StockClient stockClient) {
        this.stockClient = stockClient;
    }

    @GetMapping("/create")
    public String createOrder(@RequestParam("productId") Long productId,
                              @RequestParam("count") Integer count) {

        String stockResult = stockClient.reduce(productId, count);

        return "创建订单成功,库存服务返回:" + stockResult;
    }
}

访问:

text
http://localhost:8081/order/create?productId=1001&count=2

5.7 Feign 常见参数传递

5.7.1 Query 参数

服务提供者:

java
@GetMapping("/stock/query")
public String query(@RequestParam("productId") Long productId) {
    return "productId=" + productId;
}

Feign:

java
@GetMapping("/stock/query")
String query(@RequestParam("productId") Long productId);

5.7.2 PathVariable 参数

服务提供者:

java
@GetMapping("/stock/{productId}")
public String getById(@PathVariable("productId") Long productId) {
    return "productId=" + productId;
}

Feign:

java
@GetMapping("/stock/{productId}")
String getById(@PathVariable("productId") Long productId);

5.7.3 JSON Body 参数

DTO:

java
package com.demo.api.dto;

public class StockReduceRequest {

    private Long productId;
    private Integer count;

    public Long getProductId() {
        return productId;
    }

    public StockReduceRequest setProductId(Long productId) {
        this.productId = productId;
        return this;
    }

    public Integer getCount() {
        return count;
    }

    public StockReduceRequest setCount(Integer count) {
        this.count = count;
        return this;
    }
}

服务提供者:

java
@PostMapping("/stock/reduce-json")
public String reduceJson(@RequestBody StockReduceRequest request) {
    return "扣减库存成功,productId=" + request.getProductId()
            + ", count=" + request.getCount();
}

Feign:

java
@PostMapping("/stock/reduce-json")
String reduceJson(@RequestBody StockReduceRequest request);

5.8 Feign 超时时间配置

yaml
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connectTimeout: 3000
            readTimeout: 5000

解释:

text
connectTimeout:连接超时时间
readTimeout:读取响应超时时间

也可以针对某个服务配置:

yaml
spring:
  cloud:
    openfeign:
      client:
        config:
          stock-service:
            connectTimeout: 1000
            readTimeout: 2000

5.9 Feign 日志配置

配置日志级别:

yaml
logging:
  level:
    com.demo.order.client: DEBUG

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            loggerLevel: FULL

常见日志级别:

text
NONE:不记录日志
BASIC:只记录请求方法、URL、响应状态码、执行时间
HEADERS:记录 BASIC + 请求响应头
FULL:记录请求响应头、Body、元数据

5.10 Feign 常见坑

坑 1:忘记加 @EnableFeignClients

现象:

text
No qualifying bean of type 'xxxClient'

解决:

java
@EnableFeignClients

坑 2:Feign 接口路径和服务端路径不一致

现象:

text
404 Not Found

解决:

text
检查 @GetMapping、@PostMapping、@RequestMapping 路径。

坑 3:RequestParam 忘记写 value

建议始终写完整:

java
@RequestParam("productId") Long productId

不要写成:

java
@RequestParam Long productId

坑 4:服务名写错

java
@FeignClient(name = "stock-service")

要和 Nacos 注册中心中的服务名一致:

yaml
spring:
  application:
    name: stock-service

Released under the MIT License.