9.3、Feign客户端定义
Feign 客户端定义
定义 Feign 客户端是使用 OpenFeign 的核心。本节将学习 Feign 客户端定义。
本节将学习:@FeignClient 注解、接口定义规范、请求映射,以及参数绑定。
@FeignClient 注解
基础用法
@FeignClient(name = "user-service") public interface UserServiceClient { @GetMapping("/api/users/{id}") User getUser(@PathVariable Long id); }
注解属性
@FeignClient 属性:
name:服务名称(必填)url:服务地址(可选)path:请求路径前缀(可选)fallback:降级处理类(可选)configuration:配置类(可选)
完整配置
@FeignClient( name = "user-service", url = "http://localhost:8080", path = "/api", fallback = UserServiceFallback.class, configuration = FeignConfig.class ) public interface UserServiceClient { // ... }
接口定义规范
接口要求
接口定义要求:
- 必须是接口
- 方法必须有 Spring MVC 注解
- 支持方法重载
- 支持泛型
示例
@FeignClient(name = "user-service") public interface UserServiceClient { @GetMapping("/api/users/{id}") User getUser(@PathVariable Long id); @PostMapping("/api/users") User createUser(@RequestBody User user); @PutMapping("/api/users/{id}") void updateUser(@PathVariable Long id, @RequestBody User user); @DeleteMapping("/api/users/{id}") void deleteUser(@PathVariable Long id); }
请求映射
HTTP 方法
支持的 HTTP 方法:
@GetMapping:GET 请求@PostMapping:POST 请求@PutMapping:PUT 请求@DeleteMapping:DELETE 请求@PatchMapping:PATCH 请求
路径映射
@FeignClient(name = "user-service") public interface UserServiceClient { @GetMapping("/api/users") List<User> getUsers(); @GetMapping("/api/users/{id}") User getUser(@PathVariable Long id); @GetMapping("/api/users/{id}/orders") List<Order> getUserOrders(@PathVariable Long id); }
请求头
@GetMapping(value = "/api/users", headers = {"Authorization=Bearer {token}"}) List<User> getUsers(@RequestHeader("Authorization") String token);
参数绑定
路径变量
@GetMapping("/api/users/{id}") User getUser(@PathVariable("id") Long id);
请求参数
@GetMapping("/api/users") List<User> getUsers(@RequestParam("name") String name, @RequestParam("age") Integer age);
请求体
@PostMapping("/api/users") User createUser(@RequestBody User user);
请求头
@GetMapping("/api/users") List<User> getUsers(@RequestHeader("Authorization") String token);
复杂对象
@GetMapping("/api/users") List<User> getUsers(@SpringQueryMap UserQuery query);
在商城项目中定义 Feign 客户端
步骤1:在订单服务中添加 OpenFeign 依赖
文件路径: mall-microservices/order-service/pom.xml
<!-- OpenFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
步骤2:启用 OpenFeign
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/OrderServiceApplication.java
package com.mall.orderservice; import org.mybatis.spring.annotation.MapperScan; 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 @MapperScan("com.mall.orderservice.mapper") public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
步骤3:定义用户服务 Feign 客户端
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/feign/UserServiceClient.java
package com.mall.orderservice.feign; import com.mall.orderservice.common.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "user-service") public interface UserServiceClient { /** * 根据用户ID查询用户信息 */ @GetMapping("/api/users/{id}") Result getUserById(@PathVariable("id") Long id); /** * 根据用户名查询用户 */ @GetMapping("/api/users/username/{username}") Result getUserByUsername(@PathVariable("username") String username); /** * 根据邮箱查询用户 */ @GetMapping("/api/users/email/{email}") Result getUserByEmail(@PathVariable("email") String email); /** * 验证用户是否存在 */ @GetMapping("/api/users/{id}/exists") Boolean userExists(@PathVariable("id") Long id); }
步骤4:定义商品服务 Feign 客户端
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/feign/ProductServiceClient.java
package com.mall.orderservice.feign; import com.mall.orderservice.common.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "product-service") public interface ProductServiceClient { /** * 根据商品ID查询商品信息 */ @GetMapping("/api/products/{id}") Result getProductById(@PathVariable("id") Long id); /** * 查询商品列表(分页) */ @GetMapping("/api/products") Result getProducts( @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "size", defaultValue = "10") Integer size, @RequestParam(value = "categoryId", required = false) Long categoryId, @RequestParam(value = "keyword", required = false) String keyword ); /** * 根据商品编码查询商品 */ @GetMapping("/api/products/code/{productCode}") Result getProductByCode(@PathVariable("productCode") String productCode); }
步骤5:更新订单服务实现(使用 Feign 替代 RestTemplate)
文件路径: mall-microservices/order-service/src/main/java/com/mall/orderservice/service/impl/OrderServiceImpl.java
package com.mall.orderservice.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mall.orderservice.common.Result; import com.mall.orderservice.entity.Order; import com.mall.orderservice.feign.ProductServiceClient; import com.mall.orderservice.feign.UserServiceClient; import com.mall.orderservice.mapper.OrderMapper; import com.mall.orderservice.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private UserServiceClient userServiceClient; @Autowired private ProductServiceClient productServiceClient; @Override @Transactional(rollbackFor = Exception.class) public Order createOrder(Order order) { // 1. 验证用户是否存在(使用 Feign 客户端) Result userResult = userServiceClient.getUserById(order.getUserId()); if (userResult == null || userResult.getCode() != 200) { throw new RuntimeException("User not found: " + order.getUserId()); } // 2. 验证商品是否存在(如果需要) // Result productResult = productServiceClient.getProductById(productId); // 3. 生成订单号 order.setOrderNo(generateOrderNo()); // 4. 设置订单状态 order.setStatus(0); // 待支付 order.setPaymentStatus(0); // 未支付 // 5. 保存订单 save(order); return order; } private String generateOrderNo() { return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }
步骤6:删除旧的 RestTemplate 客户端
可以删除以下文件:
mall-microservices/order-service/src/main/java/com/mall/orderservice/client/UserServiceClient.javamall-microservices/order-service/src/main/java/com/mall/orderservice/client/ProductServiceClient.javamall-microservices/order-service/src/main/java/com/mall/orderservice/config/RestTemplateConfig.java(可选,如果不再需要)
Feign vs RestTemplate 对比
使用 Feign 的优势:
- 代码更简洁:接口定义,无需手动构建 HTTP 请求
- 类型安全:编译时检查,减少运行时错误
- 易于维护:接口定义清晰,易于理解和修改
- 自动集成:自动集成 LoadBalancer,无需额外配置
代码对比:
RestTemplate 方式:
String url = "http://user-service/api/users/" + userId; Result result = restTemplate.getForObject(url, Result.class);
Feign 方式:
Result result = userServiceClient.getUserById(userId);
测试 Feign 客户端
测试步骤
1. 启动所有服务:
# 启动 Nacos cd mall-microservices/docker/nacos docker-compose -f docker-compose-simple.yml up -d # 启动用户服务 cd mall-microservices/user-service mvn spring-boot:run # 启动商品服务 cd mall-microservices/product-service mvn spring-boot:run # 启动订单服务 cd mall-microservices/order-service mvn spring-boot:run
2. 测试订单创建(会调用用户服务):
# 先创建用户 curl -X POST http://localhost:8081/api/users/register \ -H "Content-Type: application/json" \ -d '{ "username": "testuser", "email": "test@example.com", "password": "123456" }' # 创建订单(订单服务通过 Feign 调用用户服务) curl -X POST http://localhost:8083/api/orders \ -H "Content-Type: application/json" \ -d '{ "userId": 1, "totalAmount": 100.00, "payAmount": 100.00 }'
3. 验证 Feign 调用:
查看订单服务的日志,应该能看到 Feign 的调用日志。
官方资源
- Spring Cloud OpenFeign 官方文档:https://spring.io/projects/spring-cloud-openfeign
- OpenFeign GitHub:https://github.com/OpenFeign/feign
本节小结
在本节中,我们学习了 Feign 客户端定义:
第一个是 @FeignClient 注解。 使用 @FeignClient 定义 Feign 客户端,包括注解属性和完整配置。
第二个是接口定义规范。 Feign 客户端接口的定义规范,包括接口要求和使用示例。
第三个是请求映射。 如何使用 Spring MVC 注解定义请求映射,支持各种 HTTP 方法。
第四个是参数绑定。 如何绑定路径变量、请求参数、请求体等不同类型的参数。
第五个是在商城项目中的应用。 在订单服务中定义了用户服务和商品服务的 Feign 客户端,并更新了订单服务实现,使用 Feign 替代了 RestTemplate。
第六个是 Feign vs RestTemplate 对比。 了解了使用 Feign 的优势,代码更简洁、类型安全、易于维护。
这就是 Feign 客户端定义。现在订单服务使用 Feign 客户端调用其他服务,代码更加简洁和类型安全。在下一节,我们将学习请求参数处理,了解如何处理各种复杂的参数类型。