编码集成OpenFeign
下面笔者结合实际的编码讲解 OpenFeign
是怎样把服务通信变得像本地方法调用一样简单的。本节代码是在 spring-cloud-alibaba-multi-service-demo
模板项目的基础上修改的,具体步骤如下。
-
修改项目名称。
将项目名称修改为
spring-cloud-alibaba-openfeign-demo
,并把各个模块中pom.xml
文件的artifactId
修改为spring-cloud-alibaba-openfeign-demo
。 -
引入
OpenFeign
依赖。打开
order-service-demo
项目中的pom.xml
文件,在dependencies
标签下引入OpenFeign
依赖文件,新增代码如下:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
新增
OpenFeign
代码。在
order-service-demo
项目中新建ltd.order.cloud.newbee.openfeign
包,在openfeign
包中依次新增NewBeeGoodsDemoService
文件和NewBeeShopCartDemoService
文件,分别用于创建对商品服务和购物车服务的OpenFeign
调用。NewBeeGoodsDemoService.java
代码如下:package ltd.order.cloud.newbee.openfeign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "newbee-cloud-goods-service", path = "/goods") public interface NewBeeGoodsDemoService { @GetMapping(value = "/{goodsId}") String getGoodsDetail(@PathVariable(value = "goodsId") int goodsId); }
NewBeeShopCartDemoService.java
代码如下:package ltd.order.cloud.newbee.openfeign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "newbee-cloud-shopcart-service", path = "/shop-cart") public interface NewBeeShopCartDemoService { @GetMapping(value = "/{cartId}") String getCartItemDetail(@PathVariable(value = "cartId") int cartId); }
@FeignClient
注解的常用字段如下。-
name
:指定FeignClient
的名称,如果项目中使用了负载均衡器,则name
属性将作为服务实例的名称,用于服务发现。其作用与value
字段的作用一致。 -
value
:其作用与name
字段的作用一致。 -
url
:一般用于调试,手动指定@FeignClient
调用的地址。 -
decode404
:当发生404
错误时,如果该字段为true
,则调用decoder
进行解码,否则抛出异常。 -
configuration
:指定设置自定义的相关配置类。 -
fallback
:指定处理服务容错的类。 -
fallbackFactory
:工厂类,用于生成fallback
类实例,通过这个字段配置可以实现每个接口通用的容错逻辑,减少重复的代码。 -
path
:定义当前FeignClient
路径的统一前缀。
-
-
增加配置,启用
OpenFeign
并使FeignClient
生效。在
order-service-demo
项目的启动类上添加@EnableFeignClients
注解启用,并配置相关的FeignClient
类,代码如下:@SpringBootApplication @EnableFeignClients(basePackages = {"ltd.order.cloud.newbee.openfeign"}) public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
这里直接使用
basePackages
配置了扫描包,即ltd.order.cloud.newbee.openfeign
包中标注了@FeignClient
注解的类都会生效。也可以使用clients
字段直接指定所有的类名,代码如下:@EnableFeignClients(clients={ltd.order.cloud.newbee.openfeign.NewBeeShopCartDemoService.class,ltd.order.cloud.newbee.openfeign.NewBeeGoodsDemoService.class})
-
使用
OpenFeign
声明的接口实现服务通信。由于已经使用
OpenFeign
声明了相关接口并配置完毕,因此这里修改NewBeeCloudOrderAPI
类中远程调用HTTP
请求的方式即可。先分别注入NewBeeGoodsDemoService
和NewBeeShopCartDemoService
,然后将使用RestTemplate
工具调用商品服务和购物车服务的代码删掉,改为调用本地方法形式的代码。修改
NewBeeCloudOrderAPI
类的代码如下:@RestController public class NewBeeCloudOrderAPI { @Resource private NewBeeGoodsDemoService newBeeGoodsDemoService; @Resource private NewBeeShopCartDemoService newBeeShopCartDemoService; @GetMapping("/order/saveOrder") public String saveOrder(@RequestParam("cartId") int cartId, @RequestParam("goodsId") int goodsId) { // 简单地模拟下单流程,包括服务间的调用流程 // 调用商品服务 String goodsResult = newBeeGoodsDemoService.getGoodsDetail(goodsId); // 调用购物车服务 String cartResult = newBeeShopCartDemoService.getCartItemDetail(cartId); // 执行下单逻辑 return "success! goodsResult={" + goodsResult + "},cartResult={" + cartResult + "}"; } }
使用
OpenFeign
之前的代码如下:@RestController public class NewBeeCloudOrderAPI { @Resource private RestTemplate restTemplate; // 商品服务调用地址 private final String CLOUD_GOODS_SERVICE_URL = "http://newbee-cloud-goods-service"; // 购物车服务调用地址 private final String CLOUD_SHOPCART_SERVICE_URL = "http://newbee-cloud-shopcart-service"; @GetMapping("/order/saveOrder") public String saveOrder(@RequestParam("cartId") int cartId, @RequestParam("goodsId") int goodsId) { // 简单模拟下单流程,包括服务间的调用流程 // 调用商品服务 String goodsResult = restTemplate.getForObject(CLOUD_GOODS_SERVICE_URL + "/goods/" + goodsId, String.class); // 调用购物车服务 String cartResult = restTemplate.getForObject(CLOUD_SHOPCART_SERVICE_URL + "/shop-cart/" + cartId, String.class); // 执行下单逻辑 return "success! goodsResult={" + goodsResult + "},cartResult={" + cartResult + "}"; } }
二者的区别是很明显的:不用在业务代码里处理
HTTP
请求地址、与请求参数相关的内容,只需要暴露OpenFeign
接口,直接调用本地方法即可;代码风格上更统一、服务通信的编码也更简洁,并且二者所得到的效果是相同的。这就是真实的项目开发普遍选择OpenFeign
的原因。当然,本书所编写的测试代码,不管是传参还是响应,结果都是简单的Java
类型字符串,如果是对象类型或复杂类型的对象,使用OpenFeign
与不使用OpenFeign
的区别就更明显了。 -
进行功能测试。
启动 Nacos Server
,之后依次启动这三个项目。如果未能成功启动,则开发人员需要查看控制台中的日志是否报错,并及时确认问题和修复。启动成功后进入 Nacos
控制台,单击 “服务管理” 中的服务列表,可以看到服务列表中已经存在这三个服务的服务信息,如图 8-1 所示。

打开浏览器验证是否整合成功,在地址栏中输入如下地址:
http://localhost:8117/order/saveOrder?cartId=2022&goodsId=2035
响应结果如图 8-2 所示。

整合 OpenFeign
组件后,order-service-demo
对另外两个服务的调用没有问题,测试成功!通信组件由 RestTemplate+Spring Cloud LoadBalancer
成功替换为 OpenFeign+Spring CloudLoadBalancer
。
以 NewBeeShopCartDemoService.java
为例,该接口中定义了一个方法 getCartItemDetail()
,参数是 cartId
。同时,NewBeeShopCartDemoService.java
被 @FeignClient
注解标注,其 value
字段为 newbee-cloud-shopcart-service
。“newbee-cloud-shopcart-service” 就是购物车服务在 Nacos Server
上注册的名称。path
字段为 /shop-cart
,表示 FeignClient
路径的统一前缀。getCartItemDetail()
方法其实已经表明了调用时的通信地址,即 http://newbee-cloud-shopcart-service/shop-cart/{cartId}。读者对这个地址的写法应该很熟悉,前面几个章节中介绍过的基于服务中心的通信地址皆如此。
上层方法在调用 getCartItemDetail()
方法时,会传入一个 Int
类型的 cartId
参数,如 2022
。上述的这个请求地址就变成了 http://newbee-cloud-shopcart-service/shop-cart/2022。
读者看明白了吗?OpenFeign
组件的整合其实与之前使用 RestTemplate
这类工具的整合并没有太大区别,只是 OpenFeign
会让服务通信的编码工作变得像方法调用一样简单。在项目启动时,OpenFeign
自己生成了一些代理对象,利用这些增强的代理 Bean
来完成请求处理。先是负责解析 @FeignClient
标注接口类中这些方法的请求地址,然后依然获取负载均衡器、解析服务地址、发起请求、处理响应。与未整合 OpenFeign
组件之前相比,又多了解析 FeignClient
接口中方法调用时的服务请求地址这个步骤。
关于源码解析,可以参考笔者在前几章中的步骤。看一下与 OpenFeign
组件相关的自动配置流程中做了什么操作,请求又是如何被 OpenFeign
接管的。因篇幅有限,这里就不再赘述了。不过有一个类读者可以重点关注一下——FeignBlockingLoadBalancerClient
,打一下断点,之后根据这个类去看一下向其他服务发出请求后的方法调用栈。这个类的名称也很眼熟,读者是否能够联想到之前介绍过的一个类?对!BlockingLoadBalancerClient
类。
其实源码分析到最后,会发现请求流程仍然是建立在第 5~7 章中介绍过的知识上。微服务架构的核心基础知识是服务通信和服务治理,OpenFeign
组件或类似组件的出现及整合,大大地方便了开发人员编码,使用起来逻辑也变得更加清晰。不过底层依然还是那些知识点,只是在原有知识的基础上多做了一些封装。
任它东西南北风,我自岿然不动。