Skip to content
清晨的一缕阳光
返回

OpenFeign 声明式调用

OpenFeign 声明式调用

OpenFeign 简介

OpenFeign 是 Spring Cloud 提供的声明式 HTTP 客户端,基于 Feign 10.x 版本开发。

核心特点

工作原理

┌─────────────────┐
│  业务代码调用    │
│  @FeignClient   │
└────────┬────────┘


┌─────────────────┐
│   Feign Client  │
│   动态代理       │
└────────┬────────┘


┌─────────────────┐
│    Request      │
│    构建器        │
└────────┬────────┘


┌─────────────────┐
│  LoadBalancer   │
│  选择实例        │
└────────┬────────┘


┌─────────────────┐
│  HTTP Client    │
│  发送请求        │
└─────────────────┘

快速开始

1. 添加依赖

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

2. 启用 Feign

@SpringBootApplication
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

3. 定义 Feign Client

@FeignClient(name = "order-service")
public interface OrderClient {
    
    @GetMapping("/orders/{id}")
    Result<Order> getOrder(@PathVariable("id") Long id);
    
    @PostMapping("/orders")
    Result<Order> createOrder(@RequestBody Order order);
    
    @DeleteMapping("/orders/{id}")
    Result<Void> deleteOrder(@PathVariable("id") Long id);
}

4. 使用 Feign Client

@Service
public class UserService {
    
    @Autowired
    private OrderClient orderClient;
    
    public Order getUserOrder(Long userId) {
        Result<Order> result = orderClient.getOrder(userId);
        if (result.isSuccess()) {
            return result.getData();
        }
        throw new BusinessException("获取订单失败");
    }
}

注解详解

1. @FeignClient

@FeignClient(
    name = "order-service",              // 服务名
    url = "http://localhost:8080",       // 直接指定 URL(可选)
    contextId = "orderClient",           // 上下文 ID
    path = "/api",                       // 基础路径
    configuration = FeignConfig.class,   // 自定义配置
    fallback = OrderFallback.class,      // 降级类
    fallbackFactory = OrderFallbackFactory.class  // 降级工厂
)

2. 请求映射注解

@FeignClient(name = "user-service")
public interface UserClient {
    
    // GET 请求
    @GetMapping("/users/{id}")
    Result<User> getUser(@PathVariable("id") Long id);
    
    // POST 请求
    @PostMapping("/users")
    Result<User> createUser(@RequestBody User user);
    
    // PUT 请求
    @PutMapping("/users/{id}")
    Result<User> updateUser(
        @PathVariable("id") Long id,
        @RequestBody User user
    );
    
    // DELETE 请求
    @DeleteMapping("/users/{id}")
    Result<Void> deleteUser(@PathVariable("id") Long id);
    
    // PATCH 请求
    @PatchMapping("/users/{id}/status")
    Result<User> updateStatus(
        @PathVariable("id") Long id,
        @RequestParam("status") String status
    );
}

3. 请求参数注解

@FeignClient(name = "user-service")
public interface UserClient {
    
    // 路径变量
    @GetMapping("/users/{id}")
    Result<User> getUser(@PathVariable("id") Long id);
    
    // 查询参数
    @GetMapping("/users/search")
    Result<List<User>> searchUsers(
        @RequestParam("keyword") String keyword,
        @RequestParam(value = "page", defaultValue = "1") Integer page,
        @RequestParam(value = "size", defaultValue = "10") Integer size
    );
    
    // 请求头
    @GetMapping("/users/me")
    Result<User> getCurrentUser(@RequestHeader("Authorization") String token);
    
    // 表单数据
    @PostMapping("/users/login")
    Result<User> login(
        @RequestParam("username") String username,
        @RequestParam("password") String password
    );
    
    // 请求体
    @PostMapping("/users")
    Result<User> createUser(@RequestBody User user);
}

高级特性

1. 超时配置

feign:
  client:
    config:
      default:  # 全局配置
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: BASIC
      order-service:  # 指定服务配置
        connectTimeout: 10000
        readTimeout: 10000

2. 日志配置

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

logging:
  level:
    com.example.client: DEBUG

3. 自定义配置

@Configuration
public class FeignConfig {
    
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            requestTemplate.header("X-Request-ID", UUID.randomUUID().toString());
            requestTemplate.header("X-Timestamp", String.valueOf(System.currentTimeMillis()));
        };
    }
    
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(100, 1000, 3);
    }
}

4. 拦截器

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    
    @Autowired
    private HttpServletRequest request;
    
    @Override
    public void apply(RequestTemplate template) {
        // 传递认证信息
        String token = request.getHeader("Authorization");
        if (token != null) {
            template.header("Authorization", token);
        }
        
        // 传递用户信息
        String userId = request.getHeader("X-User-ID");
        if (userId != null) {
            template.header("X-User-ID", userId);
        }
        
        // 传递链路追踪 ID
        String traceId = MDC.get("traceId");
        if (traceId != null) {
            template.header("X-Trace-ID", traceId);
        }
    }
}

5. 错误解码器

public class CustomErrorDecoder implements ErrorDecoder {
    
    @Override
    public Exception decode(String methodKey, Response response) {
        String body = Util.toString(response.body().asReader());
        
        switch (response.status()) {
            case 400:
                return new BadRequestException(body);
            case 401:
                return new UnauthorizedException(body);
            case 403:
                return new ForbiddenException(body);
            case 404:
                return new NotFoundException(body);
            case 500:
                return new InternalServerException(body);
            default:
                return new FeignException(methodKey, response, body);
        }
    }
}

熔断降级

集成 Sentinel

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
feign:
  sentinel:
    enabled: true
@FeignClient(
    name = "order-service",
    fallback = OrderFallback.class
)
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Result<Order> getOrder(@PathVariable("id") Long id);
}

@Component
public class OrderFallback implements OrderClient {
    
    @Override
    public Result<Order> getOrder(Long id) {
        return Result.fail("服务暂时不可用");
    }
}

降级工厂

@Component
public class OrderFallbackFactory implements FallbackFactory<OrderClient> {
    
    @Override
    public OrderClient create(Throwable cause) {
        return new OrderClient() {
            @Override
            public Result<Order> getOrder(Long id) {
                log.error("调用 order-service 失败", cause);
                return Result.fail("服务暂时不可用");
            }
        };
    }
}

文件上传下载

文件上传

@FeignClient(
    name = "file-service",
    configuration = MultipartConfig.class
)
public interface FileClient {
    
    @PostMapping(value = "/files/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    Result<FileVO> uploadFile(
        @RequestPart("file") MultipartFile file,
        @RequestParam("folder") String folder
    );
}

@Configuration
public class MultipartConfig {
    
    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder());
    }
    
    @Bean
    public Encoder feignEncoder() {
        return new SpringFormEncoder();
    }
}

文件下载

@FeignClient(name = "file-service")
public interface FileClient {
    
    @GetMapping("/files/{id}")
    ResponseEntity<Resource> downloadFile(@PathVariable("id") Long id);
}

性能优化

1. 连接池配置

feign:
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50
  okhttp:
    enabled: true
    max-idle-connections: 50
    keep-alive-duration: 300

2. 压缩配置

feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

3. 缓存配置

@Configuration
public class FeignCacheConfig {
    
    @Bean
    public RequestInterceptor cacheInterceptor() {
        return template -> {
            template.header("Cache-Control", "no-cache");
        };
    }
}

测试

单元测试

@SpringBootTest
public class OrderClientTest {
    
    @Autowired
    private OrderClient orderClient;
    
    @Test
    public void testGetOrder() {
        Result<Order> result = orderClient.getOrder(1L);
        assertNotNull(result);
        assertTrue(result.isSuccess());
    }
}

Mock 测试

@SpringBootTest
@Import({MockOrderClient.class})
public class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testGetUserOrder() {
        Order order = userService.getUserOrder(1L);
        assertNotNull(order);
    }
}

@Configuration
@ConditionalOnProperty(name = "test.mock", havingValue = "true")
public class MockOrderClientConfig {
    
    @Bean
    @Primary
    public OrderClient mockOrderClient() {
        return id -> Result.success(new Order());
    }
}

常见问题

1. 404 错误

问题:Feign 调用返回 404

解决方案

2. 超时错误

问题:Feign 调用超时

解决方案

3. 中文乱码

问题:中文参数乱码

解决方案

@Bean
public RequestInterceptor charsetInterceptor() {
    return template -> {
        template.header("Content-Type", "application/json;charset=UTF-8");
    };
}

总结

OpenFeign 通过声明式接口简化了服务间调用,支持负载均衡、熔断降级、日志记录等特性。

合理使用 Feign 的配置和扩展机制,可以提高服务调用的可靠性和可维护性。

在生产环境中,建议配置合理的超时时间,实现熔断降级,并添加完善的日志记录。


分享这篇文章到:

上一篇文章
Spring Boot 项目结构规范
下一篇文章
Spring Boot 性能优化实战