在Java微服务架构中,Feign是常用的声明式HTTP客户端工具,实际开发中经常需要调用不同服务的分页查询接口。不同接口的分页参数命名、返回数据结构往往存在差异,比如有的用pageNum和pageSize,有的用current和size,返回的分页数据有的放在data.list里,有的直接放在records里,针对每个接口单独编写适配代码会导致大量冗余。

传统分页调用的问题
传统方式下,每个分页接口都需要定义对应的Feign客户端方法,还要单独编写参数组装和结果解析逻辑,示例代码如下:
// 传统Feign客户端定义
@FeignClient(name = "user-service")
public interface UserFeignClient {
@GetMapping("/users/page")
UserPageResult queryUserPage(@RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize, @RequestParam("name") String name);
}
// 调用逻辑
public List<User> getUsers(String name, Integer pageNum, Integer pageSize) {
UserPageResult result = userFeignClient.queryUserPage(pageNum, pageSize, name);
// 单独解析结果
return result.getData().getList();
}如果新增一个订单分页接口,参数用current和size,结果放在records里,就需要再写一套类似的代码,扩展性很差。
通用化方案设计
我们可以引入函数式接口,分别抽象分页参数绑定和结果数据抽取两个逻辑,实现通用化的调用能力。
定义核心函数式接口
首先定义两个函数式接口,分别处理参数绑定和结果抽取:
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
// 分页参数绑定接口,将分页参数和额外查询参数绑定到请求参数Map中
@FunctionalInterface
public interface PageParamBinder extends BiConsumer<Map<String, Object>, Integer, Integer> {
}
// 分页结果抽取接口,从接口返回结果中抽取分页数据列表
@FunctionalInterface
public interface PageResultExtractor<T, R> extends Function<R, List<T>> {
}通用Feign调用工具类
编写通用调用工具类,接收Feign方法引用、参数绑定器、结果抽取器,完成统一调用:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
public class FeignPageCaller {
/**
* 通用分页调用方法
* @param feignMethod Feign接口的方法引用,接收参数Map返回接口结果
* @param paramBinder 分页参数绑定器
* @param resultExtractor 结果数据抽取器
* @param pageNum 页码
* @param pageSize 每页条数
* @param extraParams 额外查询参数
* @param <T> 返回数据实体类型
* @param <R> 接口返回结果类型
* @return 分页数据列表
*/
public static <T, R> List<T> callPageApi(
BiFunction<Map<String, Object>, R> feignMethod,
PageParamBinder paramBinder,
PageResultExtractor<T, R> resultExtractor,
Integer pageNum,
Integer pageSize,
Map<String, Object> extraParams) {
// 组装请求参数
Map<String, Object> params = new HashMap<>(extraParams);
paramBinder.accept(params, pageNum, pageSize);
// 调用Feign接口
R result = feignMethod.apply(params);
// 抽取结果数据
return resultExtractor.apply(result);
}
}方案使用示例
以之前的用户分页和订单分页两个场景为例,展示如何使用通用方案:
用户分页接口适配
用户接口分页参数为pageNum和pageSize,结果在data.list中:
// Feign客户端定义,参数用Map接收
@FeignClient(name = "user-service")
public interface UserFeignClient {
@GetMapping("/users/page")
UserPageResult queryUserPage(@RequestParam Map<String, Object> params);
}
// 参数绑定器实现
PageParamBinder userParamBinder = (params, pageNum, pageSize) -> {
params.put("pageNum", pageNum);
params.put("pageSize", pageSize);
};
// 结果抽取器实现
PageResultExtractor<User, UserPageResult> userResultExtractor = result -> result.getData().getList();
// 调用示例
public List<User> getUsers(String name, Integer pageNum, Integer pageSize) {
Map<String, Object> extraParams = new HashMap<>();
extraParams.put("name", name);
return FeignPageCaller.callPageApi(
userFeignClient::queryUserPage,
userParamBinder,
userResultExtractor,
pageNum,
pageSize,
extraParams
);
}订单分页接口适配
订单接口分页参数为current和size,结果在records中:
// 订单Feign客户端
@FeignClient(name = "order-service")
public interface OrderFeignClient {
@GetMapping("/orders/page")
OrderPageResult queryOrderPage(@RequestParam Map<String, Object> params);
}
// 参数绑定器实现
PageParamBinder orderParamBinder = (params, pageNum, pageSize) -> {
params.put("current", pageNum);
params.put("size", pageSize);
};
// 结果抽取器实现
PageResultExtractor<Order, OrderPageResult> orderResultExtractor = result -> result.getRecords();
// 调用示例
public List<Order> getOrders(String userId, Integer pageNum, Integer pageSize) {
Map<String, Object> extraParams = new HashMap<>();
extraParams.put("userId", userId);
return FeignPageCaller.callPageApi(
orderFeignClient::queryOrderPage,
orderParamBinder,
orderResultExtractor,
pageNum,
pageSize,
extraParams
);
}方案优势
- 复用性强:新增分页接口只需要实现两个简单的函数式接口,无需修改通用调用逻辑
- 扩展性好:如果需要支持更多分页参数格式,只需要新增对应的参数绑定器即可
- 代码简洁:避免了大量重复的参数组装和结果解析代码,降低维护成本
该方案通过函数式接口解耦了分页参数绑定和数据抽取逻辑,实现了Feign分页调用的通用化,适合大多数需要调用多个不同分页API的场景。