声明式微服务通信新范式:OpenFeign如何简化RestTemplate调用
一、OpenFeign介绍
OpenFeign 是一个声明式 Web Service 客户端框架,它极大地简化了微服务之间的调用过程。通过 OpenFeign,开发者可以像在 Controller 中调用 Service 一样轻松实现服务间通信 - 只需定义接口并添加相应注解即可完成远程调用
Spring Cloud Feign 是 Spring 对 Feign 的封装实现,将其集成到 Spring Cloud 生态系统中
受 Feign 项目更名影响,Spring Cloud Feign 提供两个 starter 包:
- spring-cloud-starter-feign
- spring-cloud-starter-openfeign
鉴于 Feign 已停止维护,推荐使用 spring-cloud-starter-openfeign 作为项目依赖
Spring cloud OpenFeign 官方文档https://spring.io/projects/spring-cloud-openfeign/
二、简单演示
我们将通过OpenFeign框架逐步修改订单服务中调用商品服务的代码逻辑,以下针对订单服务进行更改
2.1、添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2、添加注解
在order-service的启动类中添加@EnableFeignClients注解,以启用OpenFeign功能
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}
}
2.3、编写客户端
定义一个新增接口,用于声明获取商品信息的方法(该方法与订单服务Controller层功能一致,即通过注册中心远程调用商品服务Controller中的商品信息获取方法)
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi {@RequestMapping("/{id}")ProductInfo getProductInfo(@PathVariable("id") Integer productId);
}
@FeignClient 注解用于声明式 REST 客户端接口,主要参数说明:
value/name:指定目标微服务的名称(与服务注册中心中注册的服务名一致),用于服务发现。Feign 会通过服务名从注册中心获取服务实例列表,并默认通过 Spring Cloud LoadBalancer 实现负载均衡(若未显式配置其他负载均衡器)。若需直接访问固定地址(不经过服务发现),可通过 url 属性指定具体 URL
path:为当前 Feign 接口配置统一的请求路径前缀。接口中方法的路径会与该前缀拼接,形成完整的请求路径。例如示例中 path = "/product",方法 @RequestMapping(" / { id } ") 的完整路径为 /product/ { id }
2.4、远程调用
在订单服务的Service层修改远程调用代码:注入ProductApi对象,进行远程调用,即可避免手动拼接URL,减少可能发生的拼接错误
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate ProductApi productApi;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);ProductInfo productInfo=productApi.getProductInfo(orderInfo.getProductId());
// 代码注释为源代码
// String url="http://product-service/product/"+orderInfo.getProductId();
// ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}
2.5、测试
启动服务后并观察Nacos
访问 127.0.0.1:8080/order/1 ,可发现通过OpenFeign远程调用成功
三、OpenFeign的参数传递
3.1、传递单个参数
先在服务端定义相应的接口,在商品服务的Controller层中,增加一个获取参数功能
@RequestMapping("/p1")public String p1(Integer id){return "接收到参数,id"+id;}
Feign的客户端进行声明 p1 方法
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi {@RequestMapping("/{id}")ProductInfo getProductInfo(@PathVariable("id") Integer productId);@RequestMapping("p1")String p1(@RequestParam("id") Integer id);
}
客户端通过Feign发起远程调用
@RestController
@RequestMapping("/feign")
public class FeignController {@Autowiredprivate ProductApi productApi;@RequestMapping("/o1")public String o1(Integer id){return productApi.p1(id);}
}
当前代码调用关系为:客户端 o1 ——> Feign的 p1 ——> 服务端的 p1,访问:127.0.0.1:8080/feign/o1?id=3 观察结果:
3.2、传递多个参数
步骤与传递单个参数类似:先在服务端定义相应的接口,然后Feign客户端声明 p2 方法,再进行远程调用,代码如下
@RequestMapping("/p2")public String p2(Integer age,String name){return "今年 "+name+" 刚满 "+age;}
@RequestMapping("/p2")String p2(@RequestParam("age") Integer age,@RequestParam("name") String name);
@RequestMapping("/o2")public String o2(Integer age,String name){return productApi.p2(age,name);}
最后重启服务,访问: 127.0.0.1:8080/feign/o2?age=18&name=“小美” 观察结果:
3.3、传递对象
//服务端定义一个接口@RequestMapping("/p3")public String p3(ProductInfo productInfo){return "传递对象:"+productInfo.toString();}//Feign客户端声明该方法@RequestMapping("/p3")String p3(@SpringQueryMap ProductInfo productInfo);//进行远程调用@RequestMapping("/o3")public String o3(){ProductInfo productInfo=new ProductInfo();productInfo.setId(222);productInfo.setProductName("卡塞尔学院校服");return productApi.p3(productInfo);}
访问相应链接,观察结果:
3.4、传递JSON
//服务端定义一个接口@RequestMapping("/p4")public String p4(@RequestBody ProductInfo productInfo){return "传递JSON:"+productInfo.toString();}//Feign客户端声明该方法@RequestMapping("/p4")String p4(@RequestBody ProductInfo productInfo);//进行远程调用@RequestMapping("/o4")public String o4(){ProductInfo productInfo=new ProductInfo();productInfo.setId(333);productInfo.setProductName("白色连衣裙");return productApi.p4(productInfo);}
访问相应链接,观察结果: