SpringCloud 微服务架构与 OpenFeign

注:完整笔记可在 https://github.com/WuSangui571/cloud-demo 中的 README.md 文件浏览,此处发表的是经由 AI 润色过的精简版。

1. OpenFeign 概述

OpenFeign 是一种声明式 REST 客户端,与传统的编程式 REST 客户端(如 RestTemplate)相对。相较于 RestTemplate 手动编码的远程调用流程,OpenFeign 通过注解简化操作,复用 SpringMVC 的请求映射机制,提升开发效率。

1.1 对比 RestTemplate

传统的 RestTemplate 远程调用流程包括:

  1. 使用 DiscoveryClient 获取微服务实例列表。

  2. 选择实例(可结合负载均衡)。

  3. 通过 RestTemplate 发送请求并获取响应。

例如,之前的代码实现如下:

private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId) {
   String url = "http://service-product/product/" + productId;
   return restTemplate.getForObject(url, Product.class);
}

OpenFeign 则通过注解定义远程调用,无需手动处理实例选择与请求发送,自动支持负载均衡。

1.2 核心注解

  • @FeignClient:指定远程服务名称。

  • @GetMapping / @PostMapping / @DeleteMapping:定义请求方法。

  • @RequestHeader / @RequestParam / @RequestBody:携带请求数据。

  • 响应模型:指定返回数据结构。

2. OpenFeign 远程调用实践

本节通过订单服务调用商品服务的案例,展示 OpenFeign 的基本使用。

2.1 配置与实现

  1. 添加依赖

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 启用 Feign 功能

    • 在订单服务主类中添加 @EnableFeignClients 注解:

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

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderMainApplication {
   public static void main(String[] args) {
       SpringApplication.run(OrderMainApplication.class, args);
  }
}
  1. 定义 Feign 客户端

    • 创建接口,模拟商品服务调用:

package com.sangui.order.feign;

import com.sangui.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
* @author sangui
* @createTime 2025-09-24
* @description 商品服务 Feign 客户端接口
* @version 1.0
*/
@FeignClient("service-product")
public interface ProductFeignClient {
   @GetMapping("/product/{productId}")
   Product getProductById(@PathVariable("productId") Long productId);
}
  1. 使用 Feign 客户端

    • 在订单服务实现类中调用:

package com.sangui.order.service.impl;

import com.sangui.order.bean.Order;
import com.sangui.order.feign.ProductFeignClient;
import com.sangui.order.service.OrderService;
import com.sangui.product.bean.Product;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Arrays;

/**
* @author sangui
* @createTime 2025-09-22
* @description 订单服务实现
* @version 1.0
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
   @Resource
   private ProductFeignClient productFeignClient;

   @Override
   public Order createOrder(Long productId, Long userId) {
       Product product = productFeignClient.getProductById(productId);
       Order order = new Order();
       order.setId(1011L);
       order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNumber())));
       order.setUserId(userId);
       order.setNickName("张三");
       order.setAddress("北京");
       order.setProductList(Arrays.asList(product));
       return order;
  }
}
  1. 验证

    • 访问 http://localhost:8000/create?productId=1&userId=2,返回数据如下,证明远程调用成功:

{
 "id": 1011,
 "totalAmount": 65,
 "userId": 2,
 "nickName": "张三",
 "address": "北京",
 "productList": [
  {
     "id": 1,
     "price": 32.5,
     "productName": "创可贴",
     "number": 2
  }
]
}

提示:编写 Feign 客户端时,可直接复制目标服务 Controller 的方法签名和注解,简化开发。

2.2 调用第三方 API

以墨迹天气 API 为例,展示 OpenFeign 调用外部服务的流程。

  1. API 信息

    • 请求方式:POST

    • 地址:http://aliv18.data.moji.com/whapi/json/alicityweather/condition

    • 参数:

      • cityId:如沈阳浑南为 284698

      • token:固定值 50b53ff8dd7d9fa320d3d3ca32cf8ed1

    • 认证:Header 中的 Authorization(使用 API 密钥)。

  2. 定义 Feign 客户端

    • 由于为第三方 API,需指定完整 URL:

package com.sangui.order.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

/**
* @author sangui
* @createTime 2025-09-24
* @description 天气服务 Feign 客户端接口
* @version 1.0
*/
@FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com")
public interface WeatherFeignClient {
   @PostMapping("/whapi/json/alicityweather/condition")
   String getWeather(@RequestHeader("Authorization") String auth,
                     @RequestParam("cityId") String cityId,
                     @RequestParam("token") String token);
}
  1. 测试调用

    • 编写测试类:

package com.sangui.order;

import com.sangui.order.feign.WeatherFeignClient;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

/**
* @author sangui
* @createTime 2025-09-24
* @description 天气服务测试
* @version 1.0
*/
@SpringBootTest
public class WeatherTest {
   @Resource
   private WeatherFeignClient weatherFeignClient;

   @Test
   void getWeatherTest() {
       String weather = weatherFeignClient.getWeather("APPCODE xxxxxxxxxxxxxxxxxxxxx",
               "284698", "50b53ff8dd7d9fa320d3d3ca32cf8ed1");
       System.out.println("沈阳浑南的天气实况:" + weather);
  }
}
  1. 输出结果

    沈阳浑南的天气实况:{"code":0,"data":{"city":{"cityId":284698,"counname":"中国",...},"msg":"success","rc":{"c":0,"p":"success"}}
  2. 负载均衡分析

    • 内部微服务调用使用客户端负载均衡(OpenFeign 自动选择实例)。

    • 第三方 API 调用依赖服务端负载均衡(由墨迹天气服务器处理)。

3. OpenFeign 进阶用法

3.1 日志配置

  • 通过配置文件启用

logging:
level:
com.sangui.order.feign: debug
  • 注册日志级别 Bean

import feign.Logger;
import org.springframework.context.annotation.Bean;

@Bean
public Logger.Level feignlogLevel() {
return Logger.Level.FULL;
}
  • 日志示例(调用天气 API):

    2025-09-24T15:58:17.077+08:00 DEBUG ... [WeatherFeignClient#getWeather] ---> POST http://aliv18.data.moji.com/whapi/json/alicityweather/condition?...
    ...
    2025-09-24T15:58:17.265+08:00 DEBUG ... [WeatherFeignClient#getWeather] <--- END HTTP (582-byte body)

3.2 超时控制

  • 超时类型

    • 连接超时connectTimeout):默认 10 秒,控制连接建立时间。

    • 读取超时readTimeout):默认 60 秒,控制请求处理与响应时间。

  • 模拟超时

    • service-product 中添加延迟:

package com.sangui.product.service.impl;

import com.sangui.product.bean.Product;
import com.sangui.product.service.ProductService;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
* @author sangui
* @createTime 2025-09-22
* @description 商品服务实现
* @version 1.0
*/
@Service
public class ProductServiceImpl implements ProductService {
@Override
public Product getProductById(Long productId) {
try {
TimeUnit.SECONDS.sleep(100); // 模拟 100 秒延迟
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new Product(productId, new BigDecimal("32.5"), "创可贴", 2);
}
}
  • 配置超时

    • application.yaml 中调整:

spring:
cloud:
openfeign:
client:
config:
default:
connect-timeout: 1000
read-timeout: 2000
service-product:
connect-timeout: 3000
read-timeout: 5000
  • 效果:超过 60 秒(默认读取超时)后返回 500 错误。

3.3 重试机制

  • 默认策略NEVER_RETRY(不重试)。

  • 自定义重试

    • 注册 Retryer Bean:

import feign.Retryer;
import org.springframework.context.annotation.Bean;

@Bean
public Retryer retryer() {
return new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
  • 规则:间隔 100ms,最大间隔 1s,最多重试 5 次,间隔呈 1.5 倍增长。

  • 注意:重试针对同一实例,不切换负载均衡实例。

3.4 拦截器

  • 请求拦截器

    • 实现 RequestInterceptor 接口:

package com.sangui.order.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.util.UUID;

/**
* @author sangui
* @createTime 2025-09-24
* @description X-Token 请求拦截器
* @version 1.0
*/
public class XTokenRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("X-Token", UUID.randomUUID().toString());
}
}
  • 启用方式

    • 配置文件指定(仅限特定客户端):

spring:
cloud:
openfeign:
client:
config:
service-product:
request-interceptors:
- com.sangui.order.interceptor.XTokenRequestInterceptor
  • 或注册为 Bean(全局生效):

import org.springframework.stereotype.Component;

@Component
public class XTokenRequestInterceptor implements RequestInterceptor {
// 实现逻辑
}
  • 效果:请求头自动添加 X-Token

3.5 Fallback 机制

  • 作用:在超时或服务不可用时返回默认值,需结合 Sentinel。

  • 配置步骤

    1. 添加依赖

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 启用 Sentinel

feign:
sentinel:
enabled: true
  1. 定义 Fallback 类

package com.sangui.order.feign.fallback;

import com.sangui.order.feign.ProductFeignClient;
import com.sangui.product.bean.Product;
import java.math.BigDecimal;

/**
* @author sangui
* @createTime 2025-09-24
* @description 商品服务 Feign 客户端的 Fallback
* @version 1.0
*/
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long productId) {
System.out.println("Fallback...");
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNumber(0);
return product;
}
}
  1. 启用 Fallback

  • 修改 ProductFeignClient

@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping("/product/{productId}")
Product getProductById(@PathVariable("productId") Long productId);
}
  1. 验证

  • 阻塞 service-product,访问 http://localhost:8000/create?productId=1&userId=2,返回:

{
"id": 1011,
"totalAmount": 0,
"userId": 2,
"nickName": "张三",
"address": "北京",
"productList": [
{
"id": 1,
"price": 0,
"productName": "未知商品",
"number": 0
}
]
}

4. 总结

通过学习 OpenFeign,掌握了声明式远程调用的实现方法及其进阶功能,包括日志、超时控制、重试、拦截器和 Fallback。后续可结合 Sentinel 进一步优化容错能力。

  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录

    • avatar 鸣波

      泽哥,我能在你这里面发文章吗

        • avatar 三桂 博主
          回复 2025年09月25日 14:02:09   1层

          @ 鸣波 你咋也看 Java 了啊