背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Spring Cloud 微服务实战系列的 第四篇。Spring Cloud 是构建微服务架构的基石,拥有完整的服务治理生态,在云原生架构中广泛应用。本系列将从架构认知到实际工程,逐步构建一套企业级 Spring Cloud 微服务项目。若想详细学习请点击首篇博文开始,现在开始学习。

文章概览

OpenFeign 深度实战与容错机制构建:

  1. OpenFeign 声明式调用原理
  2. 复杂参数传递、多文件上传实践
  3. Feign 超时、重试、日志配置
  4. 容错降级:Fallback 结合 Sentinel/Resilience4j
  5. Feign 请求上下文透传(Token、Header)

1. OpenFeign 简介与工作原理

OpenFeign 是 Spring Cloud 官方推荐的远程调用方式,支持:

  • 声明式接口 定义
  • Ribbon/LoadBalancer 自动负载均衡
  • 与 Sentinel / Resilience4j 兼容的容错机制
  • 内置重试和日志功能

核心原理:

1
@FeignClient → 动态代理 → 发送 HTTP 请求 → 获取远程服务结果

项目模块依赖关系图

1
2
user-service ⇦⇨ user-api(定义 Feign 接口与 DTO)
task-service ⇨ 依赖 user-api,实现用户相关远程调用

配置与依赖

user-api 中定义接口和 DTO

user-api/pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
          <groupId>com.yutao</groupId>
          <artifactId>cloudtask2</artifactId>
          <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>user-api</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Feign 依赖 -->
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    
        <!-- 通用 DTO / JSON 支持 -->
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
    
</project>

UserClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.yutao.feign;

import java.util.List;

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

import com.yutao.dto.UserDTO;

@FeignClient(
    name = "user-service",
    path = "/user", // 网关 StripPrefix 已配置为1
    contextId = "userClient"
)
public interface UserClient {

    @GetMapping("/get/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);

    @PostMapping("/save")
    Boolean save(@RequestBody UserDTO user);

    @GetMapping("/list")
    List<UserDTO> listUsers();

    // 示例:传多个参数
    @GetMapping("/check")
    Boolean checkUsername(@RequestParam("username") String username, @RequestParam("email") String email);

    // 示例:文件上传
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String uploadAvatar(@RequestPart("file") org.springframework.web.multipart.MultipartFile file);
}

UserDTO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.yutao.dto;

import lombok.Data;

@Data
public class UserDTO {
    private Long id;
    private String username;
    private String email;
}

task-service 引入 user-api

task-service/pom.xml

1
2
3
4
<dependency>
  <groupId>com.yutao</groupId>
  <artifactId>user-api</artifactId>
</dependency>

TaskServiceApplication.java

1
2
3
@EnableFeignClients(basePackages = "com.yutao.userapi")
@SpringBootApplication
public class TaskServiceApplication { ... }

实战示例:任务服务远程调用用户服务

TaskController.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@RestController
@RequestMapping("/tasks")
@RequiredArgsConstructor
public class TaskController {

    private final UserClient userClient;

    @GetMapping("/assigned")
    public ResponseEntity<String> assignTask(@RequestParam Long userId) {
        UserDTO user = userClient.getUserInfo(userId);
        return ResponseEntity.ok("任务已分配给用户:" + user.getUsername());
    }
}

2. 参数传递实战(含文件上传)

GET + 对象参数(使用 @SpringQueryMap)

1
2
@GetMapping("/query")
UserDTO queryUser(@SpringQueryMap UserQueryDTO query);

文件上传(单文件)

1
2
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFile(@RequestPart("file") MultipartFile file);

3. Feign 配置:超时 / 重试 / 日志

这段配置放在 Feign 客户端调用方(例如:task-service)的 application.yaml 中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 3000
        loggerLevel: FULL
        retryer:
          maxAttempts: 3
          period: 100
          maxPeriod: 1000

可按客户端微调:如 feign.client.config.user-service.xxx

4. 容错降级:结合 Resilience4j 使用 fallback

这段配置属于 Feign 客户端调用方(例如:task-service)的中

添加依赖:

1
2
3
4
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

使用 fallback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient { ... }

@Component
public class UserClientFallback implements UserClient {
    @Override
    public UserDTO getUserInfo(Long userId) {
        return new UserDTO(-1L, "默认用户");
    }
}

5. 请求上下文透传(Token、Header)

定义 Feign 拦截器,这段配置属于 Feign 客户端调用方(例如:task-service)的中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String token = RequestContextHolder.getRequestAttributes()
                    .getAttribute("token", RequestAttributes.SCOPE_REQUEST);
        if (token != null) {
            template.header("Authorization", token.toString());
        }
    }
}

结合 gateway-service 传递 Token 到下游服务。

总结

技术点 实践总结
Feign 模块拆分 Feign 接口应集中在 user-api,服务使用时引入
超时/重试配置 使用 feign.client.config 做全局/单个微调
容错策略 推荐使用 Resilience4j fallback
Header 透传 配合 gateway 使用 RequestInterceptor