OpenFeign参数传递编码实践
已经成功整合 OpenFeign
,并且能够正常调用远程服务了。不过,整合代码演示中传递的参数和处理的响应结果都是 Java
基本类型。在真实的项目开发中,参数类型和响应结果的类型肯定不只是 String
或 Int
这种 Java
基本类型。简单类型、列表类型、简单对象类型、复杂对象类型,这些都是真实项目开发中会用到的。所以,本节单独来讲一下使用 OpenFeign
如何传递参数及如何处理结果响应。
简单类型处理
简单类型包括 Java
基本类型(如 String
类型和 Integer
类型)、数组和链表类型。接下来将用实际编码演示 OpenFeign
组件整合后,如何使用简单类型进行参数传递和响应结果的接收。
先在服务提供方编写接口,这里以 goods-service-demo
项目为例。编写第一个接口,参数为基本类型,代码如下:
@GetMapping("/goods/detail")
//传递多个参数,参数都是URL
public String goodsDetailByParams(@RequestParam("sellStatus") int sellStatus, @RequestParam("goodsId") int goodsId) {
System.out.println("参数如下: sellStatus="+ sellStatus + ",goodsId="+
goodsId);
//根据id查询商品并返回调用端
if (goodsId < 1 || goodsId > 100000) {
return "goodsDetailByParams 查询商品为空,当前服务的端口号为:" +
applicationServerPort;
}
String goodsName = "商品" + goodsId + ",上架状态" + sellStatus;
//返回信息给调用端
return "goodsDetailByParams " + goodsName + ",当前服务的端口号为:" +
applicationServerPort;
}
接收到参数后,进行简单的处理,然后返回一个 String
类型的结果给调用端。
接着编写第二个接口和第三个接口,参数分别为 Array
(数组)类型和 List
(链表)类型,代码如下:
@GetMapping("/goods/listByIdArray")
//传递数组类型
public String[] listByIdArray(@RequestParam("goodsIds") Integer[] goodsIds) {
// 根据 goodsIds 查询商品并返回给调用端
if (goodsIds.length < 1) {
return null;
}
String[] goodsInfos = new String[goodsIds.length];
for (int i = 0; i < goodsInfos.length; i++) {
goodsInfos[i] = "商品" + goodsIds[i];
}
// 接收参数为数组,返回信息给调用端,也为数组类型
return goodsInfos;
}
@GetMapping("/goods/listByIdList")
// 传递链表类型
public List<String> listByIdList(@RequestParam("goodsIds") List<Integer> goodsIds) {
// 根据goodsIds查询商品并返回给调用端
if (CollectionUtils.isEmpty(goodsIds)) {
return null;
}
List<String> goodsInfos = new ArrayList<>();
for (int goodsId : goodsIds) {
goodsInfos.add("商品" + goodsId);
}
// 接收参数为链表,返回信息给调用端,也为链表类型
return goodsInfos;
}
接收到参数后,进行简单的数值处理,返回同样的类型给调用端。
数据传递有两条路径:消费端传给服务端,是参数传递;服务端返回给消费端,是响应结果。 所以,在接下来的代码演示中,为了一次完成两条路径的传递讲解,传参传哪种类型,响应就是哪种类型,这样就不用重复写多种代码了,也方便读者理解。
完成接口定义后,在 FeignClient
接口类中新增对应的方法,这样就可以直接调用了。打开 order-service-demo
项目,在 NewBeeGoodsDemoService.java
文件中新增三个方法,代码如下:
@GetMapping(value = "/detail")
String getGoodsDetail3(@RequestParam(value = "goodsId") int goodsId,
@RequestParam(value = "sellStatus") int sellStatus);
@GetMapping(value = "/listByIdArray")
List<String> getGoodsArray(@RequestParam(value = "goodsIds") Integer[] goodsIds);
@GetMapping(value = "/listByIdList")
List<String> getGoodsList(@RequestParam(value = "goodsIds") List<Integer> goodsIds);
这三个方法一一对应 goods-service-demo
项目中新增的三个接口,直接调用这三个方法就相当于远程调用 goods-service-demo
项目的三个接口。
另外,像 getGoodsDetail3()
、getGoodsArray()
、getGoodsList()
这些方法名称是可以自行定义的,没有强制性的规范。只要对应接口的请求方法、URL
、参数别写错就可以了。比如,goods-service-demo
项目中新增的 goodsDetailByParams()
接口方法,其请求方法是 GET
,URL
是 /goods/detail
,请求参数是基本类型,名称分别为 sellStatus
和 goodsId
。在 getGoodsDetail3()
方法中要把这些内容进行一一对应,否则是调用不到这个接口的。
在调用端编写测试方法,之后调用 FeignClient
中定义的三个方法。打开 order-service-demo
项目,在 controller
包中新建 NewBeeCloudTestSimpleParamAPI
类,代码如下:
package ltd.order.cloud.newbee.controller;
import ltd.order.cloud.newbee.openfeign.NewBeeGoodsDemoService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@RestController
public class NewBeeCloudTestSimpleParamAPI {
@Resource
private NewBeeGoodsDemoService simpleParamService;
@GetMapping("/order/simpleParamTest")
public String simpleParamTest2(@RequestParam("sellStatus") int sellStatus, @RequestParam("goodsId") int goodsId) {
String resultString = simpleParamService.getGoodsDetail3(goodsId, sellStatus);
return resultString;
}
@GetMapping("/order/listByIdArray")
public String listByIdArray() {
Integer[] goodsIds = new Integer[4];
goodsIds[0] = 1;
goodsIds[1] = 3;
goodsIds[2] = 5;
goodsIds[3] = 7;
List<String> result = simpleParamService.getGoodsArray(goodsIds);
String resultString = "";
for (String s : result) {
resultString += s + " ";
}
return resultString;
}
@GetMapping("/order/listByIdList")
public String listByIdList() {
List<Integer> goodsIds = new ArrayList<>();
goodsIds.add(2);
goodsIds.add(4);
goodsIds.add(6);
goodsIds.add(8);
List<String> result = simpleParamService.getGoodsList(goodsIds);
String resultString = "";
for (String s : result) {
resultString += s + " ";
}
return resultString;
}
}
编码完成后,依次启动 goods-service-demo
项目和 order-service-demo
项目。注意,一定要启动 Nacos Server
。在浏览器中输入如下请求地址来测试这三个接口:
请求后的结果分别如 图8-3~图8-5 所示。



与预期结果一致,测试成功!
简单对象类型处理
在实际的项目开发中,简单对象的传输是比较常见的。为什么叫简单对象呢?这种对象一般就是普通的 POJO
对象,其中的字段都是基本类型或简单类型,不会出现一个对象里包含另一个对象这种复杂的情况。复杂对象类型处理将在第 8.3.3 节进行讲解。
因为要传输对象,并且在调用方服务和被调用方服务都用到,所以需要创建一个公共模块,在这个模块中新建要传递的对象。新建一个模块并命名为 service-common
,Java
代码的包名为 ltd.newbee.cloud
。在该模块的 pom.xml
配置文件中增加 parent
标签,与上层 Maven
建立好关系。
当然,也可以不创建公共模块,而是在调用方服务和被调用方服务各自定义一个类,这样也是可以的。
接下来新建 entity
包并新建 NewBeeGoodsInfo
类,代码如下:
package ltd.common.newbee.cloud.entity;
// 简单对象实体类
public class NewBeeGoodsInfo {
private int goodsId;
private String goodsName;
private int stock;
public void setGoodsId(int goodsId) {
this.goodsId = goodsId;
}
public int getGoodsId() {
return this.goodsId;
}
public void setStock(int stock) {
this.stock = stock;
}
public int getStock() {
return this.stock;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public String getGoodsName() {
return this.goodsName;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append(" goodsName=").append(goodsName);
sb.append(", goodsId=").append(goodsId);
sb.append(", stock=").append(stock);
sb.append("]");
return sb.toString();
}
}
公共模块创建完毕后,在调用方服务和被调用方服务中引入这个公共依赖。在 goods-service-demo
和 order-service-demo
项目的 pom.xml
文件的 dependencies
标签下新增如下代码:
<dependency>
<groupId>ltd.newbee.cloud</groupId>
<artifactId>service-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
在服务提供方编写接口,依然以 goods-service-demo
项目为例。编写一个接口,参数为简单对象类型,代码如下:
@PostMapping("/goods/updNewBeeGoodsInfo")
public NewBeeGoodsInfo updNewBeeGoodsInfo (@RequestBody NewBeeGoodsInfo newBeeGoodsInfo) {
if (newBeeGoodsInfo.getGoodsId() > 0) {
int stock = newBeeGoodsInfo.getStock();
stock -= 1;
//库存减一
newBeeGoodsInfo.setStock(stock);
}
return newBeeGoodsInfo;
}
HTTP
请求方式为 POST
,使用 @RequestBody
注解来接收对象参数 NewBeeGoodsInfo
,对其中的字段进行简单处理后,将这个对象响应给调用端。
完成接口定义后,在 FeignClient
接口类中新增对应的方法,这样就可以直接调用了。打开 order-service-demo
项目,在 NewBeeGoodsDemoService.java
文件中新增如下代码:
@PostMapping(value = "/updNewBeeGoodsInfo")
NewBeeGoodsInfo updNewBeeGoodsInfo(@RequestBody NewBeeGoodsInfo newBeeGoodsInfo);
在调用端编写测试方法,之后调用 FeignClient
中定义的方法。打开 order-service-demo
项目,在 controller
包中新建 NewBeeCloudTestObjectAPI
类,代码如下:
@RestController
public class NewBeeCloudTestObjectAPI {
@Resource
private NewBeeGoodsDemoService simpleObjectService;
@GetMapping("/order/simpleObjectTest")
public String simpleObjectTest1() {
NewBeeGoodsInfo newBeeGoodsInfo = new NewBeeGoodsInfo();
newBeeGoodsInfo.setGoodsId(2022);
newBeeGoodsInfo.setGoodsName("Spring Cloud Alibaba 微服务架构");
newBeeGoodsInfo.setStock(2035);
NewBeeGoodsInfo result = simpleObjectService.updNewBeeGoodsInfo(newBeeGoodsInfo);
return result.toString();
}
}
编码完成后,依次启动 goods-service-demo
项目和 order-service-demo
项目。注意,一定要启动 Nacos Server
。在浏览器中输入如下请求地址来测试这个接口:
http://localhost:8117/order/simpleObjectTest
请求后的结果如图 8-6 所示。

在传过去的对象参数中,stock
字段为 2035
,updNewBeeGoodsInfo()
接口接收到后进行了减一的操作,所以显示在页面上的结果是正确的,这个结果也说明参数传递和结果响应的接收两个步骤都没有问题。当然,读者在测试时也可以打上断点通过 Debug
来验证各个环节。
复杂对象类型处理
从字面上理解,复杂对象类型与简单对象类型都是常见的对象类型,只是其中的字段和属性略有差别。
先打开 service-common
项目,在 entity
包下新建 NewBeeCartItem
对象,代码如下:
package ltd.common.newbee.cloud.entity;
public class NewBeeCartItem {
private int itemId;
private String cartString;
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getItemId() {
return this.itemId;
}
public void setCartString(String cartString) {
this.cartString = cartString;
}
public String getCartString() {
return this.cartString;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append("[");
sb.append(" itemId=").append(itemId);
sb.append(", cartString=").append(cartString);
sb.append("]");
return sb.toString();
}
}
然后新建 param
包并新建 ComplexObject
类,这个类就是一个复杂对象类型,包括基本类型,也包括简单对象类型,代码如下:
package ltd.common.newbee.cloud.param;
import ltd.common.newbee.cloud.entity.NewBeeCartItem;
import ltd.common.newbee.cloud.entity.NewBeeGoodsInfo;
import java.util.List;
// 复杂对象类型,包含基本类型和简单对象类型
public class ComplexObject {
private int requestNum;
private List<Integer> cartIds;
private List<NewBeeGoodsInfo> newBeeGoodsInfos;
private NewBeeCartItem newBeeCartItem;
public void setRequestNum(int requestNum) {
this.requestNum = requestNum;
}
public int getRequestNum() {
return this.requestNum;
}
public void setCartIds(List<Integer> cartIds) {
this.cartIds = cartIds;
}
public List<Integer> getCartIds() {
return this.cartIds;
}
public void setNewBeeGoodsInfos(List<NewBeeGoodsInfo> newBeeGoodsInfos) {
this.newBeeGoodsInfos = newBeeGoodsInfos;
}
public List<NewBeeGoodsInfo> getNewBeeGoodsInfos() {
return this.newBeeGoodsInfos;
}
public void setNewBeeCartItem(NewBeeCartItem newBeeCartItem) {
this.newBeeCartItem = newBeeCartItem;
}
public NewBeeCartItem getNewBeeCartItem() {
return this.newBeeCartItem;
}
@Override
public String toString() {
return "ComplexObject{" +
"requestNum=" + requestNum +
", cartIds=" + cartIds +
", newBeeGoodsInfos=" + newBeeGoodsInfos +
", newBeeCartItem=" + newBeeCartItem +
'}';
}
}
复杂对象就是对象里面包含一个或多个其他对象,其本质还是一个对象,只是在处理时要麻烦一些。比如,在 ComplexObject
类的定义中,就包含了基本类型、基本类型的链表、简单对象类型和简单对象的链表。
接下来通过实际的编码来演示复杂对象的传递。
在服务提供方编写接口,依然以 goods-service-demo
项目为例。编写一个接口,参数为复杂对象类型。在 NewBeeCloudGoodsAPI
文件中新增如下代码:
@PostMapping("/goods/testComplexObject")
public ComplexObject testComplexObject(@RequestBody ComplexObject complexObject) {
int requestNum = complexObject.getRequestNum();
requestNum -= 1;
complexObject.setRequestNum(requestNum);
// 由于字段过多,因此这里用 Debug 方式来查看接收的复杂对象参数
return complexObject;
}
HTTP
请求方式为 POST
,使用 @RequestBody
注解来接收对象参数 ComplexObject
,对其中的字段进行简单处理后,将这个对象响应给调用端。在测试时,可以通过 Debug
方式查看复杂对象中的各个属性,确认是否被正确接收。
完成接口定义后,在 FeignClient
接口类中新增对应的方法,这样就可以直接调用了。打开 order-service-demo
项目,在 NewBeeGoodsDemoService.java
文件中新增如下代码:
@PostMapping(value = "/testComplexObject")
ComplexObject testComplexObject(@RequestBody ComplexObject complexObject);
在调用端编写测试方法,之后调用 FeignClient
中定义的方法。打开 order-service-demo
项目,在 NewBeeCloudTestObjectAPI
类中新增如下代码:
@GetMapping("/order/complexTest")
public String complexTest() {
ComplexObject complexObject = new ComplexObject();
complexObject.setRequestNum(13);
List<Integer> cartIds = new ArrayList<>();
cartIds.add(2022);
cartIds.add(13);
complexObject.setCartIds(cartIds);
NewBeeCartItem newBeeCartItem = new NewBeeCartItem();
newBeeCartItem.setItemId(2023);
newBeeCartItem.setCartString("newbee cloud");
complexObject.setNewBeeCartItem(newBeeCartItem);
List<NewBeeGoodsInfo> newBeeGoodsInfos = new ArrayList<>();
NewBeeGoodsInfo newBeeGoodsInfo1 = new NewBeeGoodsInfo();
newBeeGoodsInfo1.setGoodsName("Spring Cloud Alibaba 大型微服务架构实战 (上册)");
newBeeGoodsInfo1.setGoodsId(2024);
newBeeGoodsInfo1.setStock(10000);
NewBeeGoodsInfo newBeeGoodsInfo2 = new NewBeeGoodsInfo();
newBeeGoodsInfo2.setGoodsName("Spring Cloud Alibaba 大型微服务架构项目实战 (下册)");
newBeeGoodsInfo2.setGoodsId(2025);
newBeeGoodsInfo2.setStock(10000);
newBeeGoodsInfos.add(newBeeGoodsInfo1);
newBeeGoodsInfos.add(newBeeGoodsInfo2);
complexObject.setNewBeeGoodsInfos(newBeeGoodsInfos);
// 以上这些代码相当于平时开发时的请求参数整理
ComplexObject result = simpleObjectService.testComplexObject(complexObject);
return result.toString();
}
在功能测试 complexObjectTest()
方法中,主要是对复杂对象进行构造和参数填充,之后将其传递给被调用端,被调用端返回的对象同样是一个复杂对象。
编码完成后,依次启动 goods-service-demo
项目和 order-service-demo
项目。注意,一定要启动 Nacos Server
。在浏览器中输入如下请求地址来测试这个接口:
http://localhost:8117/order/complexObjectTest
请求后的结果如图 8-7 所示。

在传递的对象参数中,requestNum
字段为 13
,testComplexObject()
接口接收后进行了减 1 的操作,而页面上的所有数据都是通过被调用端的接口响应的,这个结果也说明参数传递和结果响应的接收两个步骤都没有问题,测试完成。
通用结果类 Result
项目中使用统一的结果响应对象来处理请求的数据响应,这样做的好处是可以保证所有接口响应数据格式的统一,大大地减少处理接口响应数据的工作量,同时避免因为数据格式不统一而造成的开发问题。以后端 API
项目中的功能模块为例,有些接口需要返回简单的对象,如字符串或数字;有些接口需要返回一个复杂的对象,如用户详情接口、商品详情接口,这些接口需要返回不同的对象;有些接口需要返回列表对象或分页数据,这些对象又复杂了一些。
本项目的结果响应类代码如下:
public class Result<T> implements Serializable {
//业务码,如成功、失败、权限不足等代码,可自行定义
private int resultCode;
//返回信息,后端在进行业务处理后返回给前端一个提示信息,可自行定义
private String message;
//数据结果,泛型,可以是列表、单个对象、数字、布尔值等
private T data;
public Result() {
}
public Result(int resultCode, String message) {
this.resultCode = resultCode;
this.message = message;
}
public int getResultCode() {
return resultCode;
}
public void setResultCode(int resultCode) {
this.resultCode = resultCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"resultCode=" + resultCode +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
每次请求响应的结果都会根据以上格式进行数据封装,包括业务码、返回信息、实际的数据结果。接收该结果后对数据进行解析,并通过业务码进行相应的逻辑操作,之后获取 data
字段中的数据并进行后续的业务操作。
实际返回的数据格式示例如下:
列表数据:
{
"resultCode": 200,
"message": "SUCCESS",
"data": [
{
"id": 2,
"name": "user1",
"password": "123456"
},
{
"id": 1,
"name": "13",
"password": "12345"
}
]
}
单条数据:
{
"resultCode": 200,
"message": "SUCCESS",
"data": true
}
以上两条数据分别是返回的列表数据和单条数据,接口在进行业务处理后将返回一个 Result
类型的对象,如果用浏览器访问,可以看到一个 JSON
格式的字符串。resultCode
字段的值等于 200
时表示数据请求成功,该字段也可以自行定义,如 0
、1001
、500
等。message
字段的值为 SUCCESS
,读者也可以自行定义返回信息,如 “获取成功”、“列表数据查询成功” 等。一个返回码只表示一个含义,而 data
字段中的数据可以是一个对象数组,也可以是一个字符串、数字等类型,根据不同的业务返回不同的结果。其实这个类也算是复杂对象类型,笔者创建了一个示例供读者进行测试。因篇幅有限,这里不再赘述,读者可以自行下载本章代码来测试。