跳到主要内容

注解详解

OpenFeign 支持两种风格的注解:Spring MVC 注解和原生 Feign 注解。本章将深入讲解如何使用这些注解定义各种类型的 HTTP 请求。

推荐使用 Spring MVC 注解

Spring Cloud OpenFeign 默认使用 SpringMvcContract,支持完整的 Spring MVC 注解。这种方式与 Spring 生态无缝集成,学习成本低,是首选方案。原生 Feign 注解主要用于非 Spring 环境或需要调用非标准 REST API 的场景。

Spring MVC 注解

Spring Cloud OpenFeign 默认使用 SpringMvcContract,这意味着你可以在 Feign 接口上使用熟悉的 Spring MVC 注解。

@GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping

这些注解用于指定 HTTP 方法和请求路径:

@FeignClient(name = "user-service")
public interface UserClient {

// GET 请求:获取资源
@GetMapping("/users")
List<User> getAllUsers();

@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);

// POST 请求:创建资源
@PostMapping("/users")
User createUser(@RequestBody User user);

// PUT 请求:完整更新资源
@PutMapping("/users/{id}")
User updateUser(@PathVariable("id") Long id, @RequestBody User user);

// PATCH 请求:部分更新资源
@PatchMapping("/users/{id}")
User patchUser(@PathVariable("id") Long id, @RequestBody Map<String, Object> updates);

// DELETE 请求:删除资源
@DeleteMapping("/users/{id}")
void deleteUser(@PathVariable("id") Long id);
}

HTTP 方法选择指南

HTTP 方法用途是否幂等请求体
GET获取资源
POST创建资源
PUT完整更新资源
PATCH部分更新资源
DELETE删除资源

幂等性说明:幂等意味着多次执行相同请求,结果与执行一次相同。GET、PUT、DELETE 是幂等的,POST 不是。

@RequestMapping

@RequestMapping 是更通用的注解,可以指定 HTTP 方法:

@FeignClient(name = "user-service")
public interface UserClient {

@RequestMapping(method = RequestMethod.GET, value = "/users")
List<User> getAllUsers();

@RequestMapping(method = RequestMethod.POST, value = "/users",
consumes = "application/json", produces = "application/json")
User createUser(@RequestBody User user);

// 同时支持多种 HTTP 方法(不推荐在 Feign 中使用)
@RequestMapping(value = "/users/{id}", method = {RequestMethod.GET, RequestMethod.POST})
User getUserOrPost(@PathVariable("id") Long id);
}

@PathVariable

@PathVariable 用于绑定 URL 路径中的变量:

@FeignClient(name = "order-service")
public interface OrderClient {

// 单个路径变量
@GetMapping("/orders/{orderId}")
Order getOrder(@PathVariable("orderId") Long orderId);

// 多个路径变量
@GetMapping("/users/{userId}/orders/{orderId}")
Order getUserOrder(
@PathVariable("userId") Long userId,
@PathVariable("orderId") Long orderId
);
// 实际请求:/users/123/orders/456

// 路径变量支持正则表达式
@GetMapping("/orders/{orderId:\\d+}") // 只匹配数字
Order getNumericOrder(@PathVariable("orderId") Long orderId);
}

使用要点

  • value 属性指定路径变量的名称,必须与 URL 中的占位符一致
  • 如果方法参数名与路径变量名相同,可以省略 value 属性(但建议显式指定,避免编译时丢失参数名)

正则表达式约束

路径变量支持正则表达式约束,格式为 {变量名:正则表达式}

// 只匹配数字 ID
@GetMapping("/orders/{id:\\d+}")
Order getOrderById(@PathVariable("id") Long id);

// 只匹配特定格式的订单号(如 ORD-2024-001)
@GetMapping("/orders/{orderNo:ORD-\\d{4}-\\d{3}}")
Order getOrderByNo(@PathVariable("orderNo") String orderNo);

// 匹配 UUID 格式
@GetMapping("/resources/{uuid:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}}")
Resource getResource(@PathVariable("uuid") String uuid);
编译参数保留

如果省略 @PathVariablevalue 属性,需要确保编译时保留参数名。Maven 配置:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters> <!-- 保留参数名 -->
</configuration>
</plugin>

建议始终显式指定 value 属性,避免潜在问题。

@RequestParam

@RequestParam 用于绑定 URL 查询参数:

@FeignClient(name = "user-service")
public interface UserClient {

// 单个查询参数
@GetMapping("/users/search")
User findByUsername(@RequestParam("username") String username);
// 实际请求:/users/search?username=xxx

// 多个查询参数
@GetMapping("/users/search")
List<User> searchUsers(
@RequestParam("name") String name,
@RequestParam("status") String status
);
// 实际请求:/users/search?name=xxx&status=xxx

// 可选参数
@GetMapping("/users")
List<User> getUsers(
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "10") Integer size
);
// 实际请求:/users 或 /users?page=2&size=20

// 集合参数
@GetMapping("/users/by-ids")
List<User> getUsersByIds(@RequestParam("ids") List<Long> ids);
// 实际请求:/users/by-ids?ids=1&ids=2&ids=3

// 数组参数
@GetMapping("/users/by-names")
List<User> getUsersByNames(@RequestParam("names") String[] names);
// 实际请求:/users/by-names?names=张三&names=李四

// Map 参数(动态查询参数)
@GetMapping("/users/search")
List<User> searchUsers(@RequestParam Map<String, String> params);
// 实际请求:/users/search?key1=value1&key2=value2...
}

属性详解

属性说明默认值
value / name参数名-
required是否必填true
defaultValue默认值(设置后 required 自动变为 false)-

使用场景对比

// 场景 1:固定的查询参数
@GetMapping("/users")
List<User> getUsers(
@RequestParam("page") int page,
@RequestParam("size") int size
);

// 场景 2:可选的查询参数
@GetMapping("/users")
List<User> getUsers(
@RequestParam(value = "keyword", required = false) String keyword
);
// keyword 为 null 时,不会添加到 URL 中

// 场景 3:动态查询参数(参数不固定)
@GetMapping("/users/search")
List<User> search(@RequestParam Map<String, Object> params);

// 调用方式
Map<String, Object> params = new HashMap<>();
params.put("name", "张三");
params.put("status", "active");
params.put("dept", "IT");
userClient.search(params);
// 实际请求:/users/search?name=张三&status=active&dept=IT
集合参数的格式

默认情况下,集合参数会展开为多个同名参数。如果需要逗号分隔的格式,可以使用 @CollectionFormat 注解:

@CollectionFormat(CollectionFormat.CSV)
@GetMapping("/users/by-ids")
List<User> getUsersByIds(@RequestParam("ids") List<Long> ids);
// 实际请求:/users/by-ids?ids=1,2,3

@RequestBody

@RequestBody 用于传递请求体,通常用于 POST 和 PUT 请求:

@FeignClient(name = "user-service")
public interface UserClient {

// 传递 JSON 对象
@PostMapping("/users")
User createUser(@RequestBody User user);

// 传递字符串
@PostMapping("/users/{id}/notes")
void addNote(@PathVariable("id") Long id, @RequestBody String note);

// 传递 Map
@PostMapping("/users/batch")
List<User> batchCreate(@RequestBody List<User> users);
}

使用要点

  • 请求体会被序列化为 JSON(默认使用 Jackson)
  • 一个方法只能有一个 @RequestBody 参数

@RequestHeader

@RequestHeader 用于添加请求头:

@FeignClient(name = "api-service")
public interface ApiClient {

// 单个请求头
@GetMapping("/api/data")
String getData(@RequestHeader("Authorization") String token);

// 多个请求头
@GetMapping("/api/data")
String getData(
@RequestHeader("Authorization") String token,
@RequestHeader("X-Request-Id") String requestId
);

// 可选请求头
@GetMapping("/api/data")
String getData(
@RequestHeader(value = "X-Custom-Header", required = false) String customHeader
);

// 所有请求头(Map 形式)
@GetMapping("/api/headers")
String getHeaders(@RequestHeader Map<String, String> headers);

// 多值请求头
@GetMapping("/api/data")
String getData(@RequestHeader("Accept") List<String> acceptHeaders);
}

常用请求头示例

@FeignClient(name = "api-service")
public interface ApiClient {

// 认证头
@GetMapping("/api/users")
List<User> getUsers(@RequestHeader("Authorization") String auth);
// 调用:client.getUsers("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")

// 内容类型
@PostMapping("/api/data")
String postData(
@RequestHeader("Content-Type") String contentType,
@RequestBody String data
);
// 调用:client.postData("application/json", jsonData)

// 自定义追踪头
@GetMapping("/api/data")
String getData(
@RequestHeader("X-Trace-Id") String traceId,
@RequestHeader("X-Span-Id") String spanId
);
}
推荐使用拦截器

如果需要在多个请求中添加相同的请求头(如认证 Token),建议使用 RequestInterceptor 而不是在每个方法上添加 @RequestHeader

@Bean
public RequestInterceptor authInterceptor() {
return template -> {
template.header("Authorization", "Bearer " + tokenService.getToken());
};
}

这样可以避免重复代码,也便于统一管理。

@CookieValue

虽然 OpenFeign 没有直接提供 @CookieValue 注解,但可以通过 @RequestHeader 设置 Cookie:

@FeignClient(name = "api-service")
public interface ApiClient {

// 设置 Cookie
@GetMapping("/api/data")
String getData(@RequestHeader("Cookie") String cookie);
// 调用:client.getData("sessionId=abc123; userId=456")
}

更好的方式是使用拦截器统一处理 Cookie:

@Bean
public RequestInterceptor cookieInterceptor() {
return template -> {
// 从当前请求上下文获取 Cookie
HttpServletRequest request = getCurrentRequest();
if (request != null) {
String cookie = request.getHeader("Cookie");
if (cookie != null) {
template.header("Cookie", cookie);
}
}
};
}

### @SpringQueryMap

`@SpringQueryMap` 用于将 POJO 对象转换为查询参数:

```java
public class UserQuery {
private String name;
private String email;
private Integer status;
private Integer page = 1;
private Integer size = 10;

// getter、setter 省略
}

@FeignClient(name = "user-service")
public interface UserClient {

@GetMapping("/users/search")
List<User> searchUsers(@SpringQueryMap UserQuery query);
// 请求 URL: /users/search?name=xxx&email=xxx&status=1&page=1&size=10
}

@RequestParam Map 的区别

  • @SpringQueryMap 支持类型安全的 POJO
  • @RequestParam Map 更灵活,但没有类型检查

@MatrixVariable

@MatrixVariable 用于处理矩阵变量(路径段中的键值对)。矩阵变量是 URI 中的一种参数传递方式,格式为 ;key=value,通常用于表示资源的属性或筛选条件。

矩阵变量与查询参数的区别

查询参数:/api/cars?color=red&year=2024
矩阵变量:/api/cars;color=red;year=2024

矩阵变量的优势在于它可以出现在路径的任意位置,而不仅仅是末尾,例如 /api/cars;color=red/sedans;year=2024

基本用法

@FeignClient(name = "api-service")
public interface ApiClient {

// 单个矩阵变量
// 实际请求:/api/cars;color=red
@GetMapping("/api/cars")
List<Car> getCarsByColor(@MatrixVariable("color") String color);

// 多个矩阵变量
// 实际请求:/api/cars;color=red;year=2024
@GetMapping("/api/cars")
List<Car> getCars(
@MatrixVariable("color") String color,
@MatrixVariable("year") Integer year
);

// 带路径变量的矩阵变量
// 实际请求:/api/cars/sedans;color=red;year=2024
@GetMapping("/api/cars/{type}")
List<Car> getCarsByType(
@PathVariable("type") String type,
@MatrixVariable("color") String color,
@MatrixVariable("year") Integer year
);
}

使用 Map 接收所有矩阵变量

当矩阵变量较多时,可以使用 Map 接收:

@FeignClient(name = "api-service")
public interface ApiClient {

// 使用 Map 接收所有矩阵变量
// Map 中的键值对会被转换为 ;key1=value1;key2=value2 格式
@GetMapping("/api/cars")
List<Car> getCars(@MatrixVariable Map<String, String> filter);

// 调用示例
// Map<String, String> filter = new HashMap<>();
// filter.put("color", "red");
// filter.put("year", "2024");
// client.getCars(filter);
// 实际请求:/api/cars;color=red;year=2024
}

矩阵变量绑定到特定路径变量

当 URL 中有多个路径段时,可以使用 pathVar 属性将矩阵变量绑定到特定的路径变量:

@FeignClient(name = "api-service")
public interface ApiClient {

// 矩阵变量绑定到特定路径段
// 实际请求:/api/stores;region=north/products;category=electronics
@GetMapping("/api/{store}/products")
List<Product> getProducts(
@MatrixVariable(value = "region", pathVar = "store") String region,
@MatrixVariable(value = "category", pathVar = "products") String category
);
}

设置默认值

@FeignClient(name = "api-service")
public interface ApiClient {

// 设置默认值
@GetMapping("/api/cars")
List<Car> getCars(
@MatrixVariable(value = "color", defaultValue = "black") String color
);
// 如果不传 color,实际请求:/api/cars;color=black
}
注意事项
  1. 使用 @MatrixVariable 时,Spring MVC 需要启用矩阵变量支持(默认是禁用的)。在 Spring Boot 中需要配置:
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(true);
    }
    }
  2. 并非所有服务端都支持矩阵变量,使用前请确认目标 API 是否支持这种格式。
  3. 当使用 Map 参数时,键值对会用 = 连接,如 key=value

原生 Feign 注解

除了 Spring MVC 注解,OpenFeign 还支持原生注解。要使用原生注解,需要自定义 Contract:

配置原生 Contract

public class FeignConfig {

@Bean
public Contract feignContract() {
return new Contract.Default(); // 使用原生 Feign Contract
}
}

@RequestLine

@RequestLine 用于指定 HTTP 方法和路径:

@FeignClient(name = "user-service", configuration = FeignConfig.class)
public interface UserClient {

@RequestLine("GET /users")
List<User> getAllUsers();

@RequestLine("GET /users/{id}")
User getUserById(@Param("id") Long id);

@RequestLine("POST /users")
User createUser(User user); // 无需 @RequestBody
}

@Param

@Param 用于绑定路径变量或查询参数:

@FeignClient(name = "user-service", configuration = FeignConfig.class)
public interface UserClient {

@RequestLine("GET /users/{id}")
User getUserById(@Param("id") Long id);

@RequestLine("GET /users?name={name}&status={status}")
List<User> searchUsers(@Param("name") String name, @Param("status") String status);

// 支持自定义展开器
@RequestLine("GET /users/{id}")
User getUserById(@Param(value = "id", expander = IdExpander.class) Long id);
}

@Headers

@Headers 用于添加请求头:

@FeignClient(name = "api-service", configuration = FeignConfig.class)
public interface ApiClient {

// 方法级别
@RequestLine("GET /api/data")
@Headers({"Authorization: Bearer {token}", "Content-Type: application/json"})
String getData(@Param("token") String token);

// 接口级别(应用于所有方法)
@Headers("Accept: application/json")
public interface ApiClient {
@RequestLine("GET /api/users")
List<User> getUsers();
}
}

@Body

@Body 用于指定请求体模板:

@FeignClient(name = "user-service", configuration = FeignConfig.class)
public interface UserClient {

@RequestLine("POST /users")
@Headers("Content-Type: application/json")
@Body("%7B\"name\": \"{name}\", \"email\": \"{email}\"%7D")
User createUser(@Param("name") String name, @Param("email") String email);
}

混合使用注解

在实际项目中,推荐统一使用 Spring MVC 注解,因为它们更直观,且与 Spring 生态无缝集成。但如果需要调用非 Spring 服务,原生注解可能更灵活。

// 推荐:统一使用 Spring MVC 注解
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}

// 特殊场景:使用原生注解
@FeignClient(name = "legacy-service", configuration = NativeFeignConfig.class)
public interface LegacyClient {
@RequestLine("GET /api/v1/users/{id}")
LegacyUser getUser(@Param("id") String id);
}

文件上传

上传单个文件

@FeignClient(name = "file-service")
public interface FileClient {

@PostMapping(value = "/files/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FileResponse uploadFile(@RequestPart("file") MultipartFile file);

// 携带额外参数
@PostMapping(value = "/files/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FileResponse uploadFile(
@RequestPart("file") MultipartFile file,
@RequestParam("category") String category
);
}

上传多个文件

@FeignClient(name = "file-service")
public interface FileClient {

@PostMapping(value = "/files/batch-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
List<FileResponse> uploadFiles(@RequestPart("files") MultipartFile[] files);
}

使用 byte 数组

如果不想依赖 MultipartFile,可以使用 byte[]

@FeignClient(name = "file-service")
public interface FileClient {

@PostMapping(value = "/files/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
FileResponse uploadFile(
@RequestPart("file") byte[] file,
@RequestPart("filename") String filename
);
}

文件下载

下载为 byte 数组

@FeignClient(name = "file-service")
public interface FileClient {

@GetMapping("/files/{id}/download")
ResponseEntity<byte[]> downloadFile(@PathVariable("id") Long id);
}

下载为 Resource

@FeignClient(name = "file-service")
public interface FileClient {

@GetMapping("/files/{id}/download")
ResponseEntity<Resource> downloadFile(@PathVariable("id") Long id);
}

使用示例:

@Service
public class FileService {

private final FileClient fileClient;

public void downloadAndSave(Long fileId, String savePath) throws IOException {
ResponseEntity<Resource> response = fileClient.downloadFile(fileId);

if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
try (InputStream is = response.getBody().getInputStream();
FileOutputStream fos = new FileOutputStream(savePath)) {
IOUtils.copy(is, fos);
}
}
}
}

小结

本章详细介绍了 OpenFeign 支持的各种注解:

注解用途示例
@GetMapping定义 HTTP 方法和路径@GetMapping("/users")
@PathVariable绑定路径变量@PathVariable("id") Long id
@RequestParam绑定查询参数@RequestParam("name") String name
@RequestBody传递请求体@RequestBody User user
@RequestHeader添加请求头@RequestHeader("Auth") String token
@SpringQueryMapPOJO 转查询参数@SpringQueryMap UserQuery query
@RequestPart文件上传@RequestPart("file") MultipartFile file

下一章将深入学习 OpenFeign 的配置机制,掌握如何自定义各种行为。