跳到主要内容

注解详解

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

Spring MVC 注解

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

@GetMapping / @PostMapping / @PutMapping / @DeleteMapping

这些注解用于指定 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);

// DELETE 请求
@DeleteMapping("/users/{id}")
void deleteUser(@PathVariable("id") Long id);
}

@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
);

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

使用要点

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

@RequestParam

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

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

// 单个查询参数
@GetMapping("/users/search")
User findByUsername(@RequestParam("username") String username);

// 多个查询参数
@GetMapping("/users/search")
List<User> searchUsers(
@RequestParam("name") String name,
@RequestParam("status") String status
);
// 请求 URL: /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
);

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

// Map 参数(动态查询参数)
@GetMapping("/users/search")
List<User> searchUsers(@RequestParam Map<String, String> params);
}

使用要点

  • required 属性默认为 true,如果参数可能为空,需设置为 false
  • defaultValue 属性设置默认值,设置了默认值后 required 自动变为 false
  • 使用 Map 类型可以传递动态查询参数

@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/headers")
String getHeaders(@RequestHeader Map<String, String> headers);
}

@SpringQueryMap

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

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 用于处理矩阵变量(路径段中的键值对):

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

// 矩阵变量示例:/api/cars;color=red;year=2024
@GetMapping("/api/cars/{filter}")
List<Car> getCars(@MatrixVariable("color") String color,
@MatrixVariable("year") Integer year);

// 使用 Map 接收所有矩阵变量
@GetMapping("/api/cars/{filter}")
List<Car> getCars(@MatrixVariable Map<String, String> filter);
}
注意

使用 @MatrixVariable 时,路径占位符名称必须与变量名称一致。

原生 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 的配置机制,掌握如何自定义各种行为。