OpenFeign参数传递编码实践

已经成功整合 OpenFeign,并且能够正常调用远程服务了。不过,整合代码演示中传递的参数和处理的响应结果都是 Java 基本类型。在真实的项目开发中,参数类型和响应结果的类型肯定不只是 StringInt 这种 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() 接口方法,其请求方法是 GETURL/goods/detail,请求参数是基本类型,名称分别为 sellStatusgoodsId。在 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 所示。

image 2025 04 16 17 11 54 334
Figure 1. 图8-3/order/simpleParamTest 请求的访问结果
image 2025 04 16 17 12 09 443
Figure 2. 图8-4/order/listByIdArray 请求的访问结果
image 2025 04 16 17 12 25 487
Figure 3. 图8-5/order/listByIdList 请求的访问结果

与预期结果一致,测试成功!

简单对象类型处理

在实际的项目开发中,简单对象的传输是比较常见的。为什么叫简单对象呢?这种对象一般就是普通的 POJO 对象,其中的字段都是基本类型或简单类型,不会出现一个对象里包含另一个对象这种复杂的情况。复杂对象类型处理将在第 8.3.3 节进行讲解。

因为要传输对象,并且在调用方服务和被调用方服务都用到,所以需要创建一个公共模块,在这个模块中新建要传递的对象。新建一个模块并命名为 service-commonJava 代码的包名为 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-demoorder-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 所示。

image 2025 04 16 17 24 06 401
Figure 4. 图8-6/order/simpleObjectTest 请求的访问结果

在传过去的对象参数中,stock 字段为 2035updNewBeeGoodsInfo() 接口接收到后进行了减一的操作,所以显示在页面上的结果是正确的,这个结果也说明参数传递和结果响应的接收两个步骤都没有问题。当然,读者在测试时也可以打上断点通过 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 所示。

image 2025 04 16 17 34 29 990
Figure 5. 图8-7/order/complexObjectTest 请求的访问结果

在传递的对象参数中,requestNum 字段为 13testComplexObject() 接口接收后进行了减 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 时表示数据请求成功,该字段也可以自行定义,如 01001500 等。message 字段的值为 SUCCESS,读者也可以自行定义返回信息,如 “获取成功”、“列表数据查询成功” 等。一个返回码只表示一个含义,而 data 字段中的数据可以是一个对象数组,也可以是一个字符串、数字等类型,根据不同的业务返回不同的结果。其实这个类也算是复杂对象类型,笔者创建了一个示例供读者进行测试。因篇幅有限,这里不再赘述,读者可以自行下载本章代码来测试。