在微服务架构中,Feign是常用的HTTP客户端工具,当需要调用多个带分页参数的接口时,如果每个接口都单独编写分页参数处理、结果解析的逻辑,会产生大量重复代码。通过函数式编程的方式封装通用逻辑,可以大幅降低重复开发成本,提升代码复用性。

分页调用的通用需求分析
多数分页接口的请求参数都包含页码、每页条数,响应结果都包含当前页数据、总条数、总页数等字段,不同接口的差异主要在于业务参数和返回的具体数据类型。我们可以将共性的分页逻辑抽离,把差异部分通过函数式接口交给调用方自定义。
核心函数式接口定义
首先定义两个函数式接口,分别处理请求参数绑定和响应结果解析:
// 分页参数绑定接口,将通用分页参数和业务参数绑定到请求对象
@FunctionalInterface
public interface PageParamBinder<T, R> {
R bind(T businessParam, Integer pageNum, Integer pageSize);
}
// 分页结果解析接口,从Feign响应中提取分页相关信息
@FunctionalInterface
public interface PageResultParser<R, D> {
PageResult<D> parse(R response);
}通用分页结果封装
定义统一的分页结果模型,适配不同接口的返回结构:
public class PageResult<T> {
private Integer pageNum;
private Integer pageSize;
private Long total;
private Integer totalPages;
private List<T> data;
// 省略getter、setter和构造方法
}通用分页调用核心实现
接下来封装通用的分页调用方法,接收Feign调用函数、参数绑定器、结果解析器、业务参数、分页参数,返回统一的分页结果:
public class FeignPageCaller {
// 通用分页调用方法
public static <T, P, R, D> PageResult<D> callPageApi(
// Feign接口调用函数,接收绑定的请求参数,返回Feign响应
Function<P, R> feignCall,
// 参数绑定器
PageParamBinder<T, P> paramBinder,
// 结果解析器
PageResultParser<R, D> resultParser,
// 业务参数
T businessParam,
// 页码,默认第一页
Integer pageNum,
// 每页条数,默认10条
Integer pageSize) {
// 设置默认分页参数
if (pageNum == null || pageNum < 1) {
pageNum = 1;
}
if (pageSize == null || pageSize < 1) {
pageSize = 10;
}
// 绑定请求参数
P requestParam = paramBinder.bind(businessParam, pageNum, pageSize);
// 调用Feign接口
R feignResponse = feignCall.apply(requestParam);
// 解析并返回分页结果
return resultParser.parse(feignResponse);
}
}实际使用示例
定义Feign接口
首先定义业务对应的Feign接口,这里以用户查询接口为例:
@FeignClient(name = "user-service", url = "http://127.0.0.1:8080")
public interface UserFeignClient {
@GetMapping("/users/page")
UserPageResponse queryUserPage(@SpringQueryMap UserPageRequest request);
}定义请求和响应模型
// 用户分页请求
public class UserPageRequest {
private String username;
private Integer age;
private Integer pageNum;
private Integer pageSize;
// 省略getter、setter
}
// 用户分页响应
public class UserPageResponse {
private Integer code;
private String msg;
private UserPageData data;
// 省略getter、setter
}
// 响应中的数据部分
public class UserPageData {
private Long total;
private Integer totalPages;
private List<UserDTO> list;
// 省略getter、setter
}
// 用户DTO
public class UserDTO {
private Long id;
private String username;
private Integer age;
// 省略getter、setter
}调用通用分页方法
在业务代码中调用封装好的通用方法,传入自定义的参数绑定和结果解析逻辑:
@Service
public class UserService {
@Autowired
private UserFeignClient userFeignClient;
public PageResult<UserDTO> queryUserPage(String username, Integer age, Integer pageNum, Integer pageSize) {
// 业务参数对象
Map<String, Object> businessParam = new HashMap<>();
businessParam.put("username", username);
businessParam.put("age", age);
return FeignPageCaller.callPageApi(
// Feign调用函数
request -> userFeignClient.queryUserPage(request),
// 参数绑定逻辑:将业务参数和分页参数绑定到UserPageRequest
(param, pNum, pSize) -> {
UserPageRequest request = new UserPageRequest();
request.setUsername((String) param.get("username"));
request.setAge((Integer) param.get("age"));
request.setPageNum(pNum);
request.setPageSize(pSize);
return request;
},
// 结果解析逻辑:从UserPageResponse中提取分页信息
response -> {
PageResult<UserDTO> result = new PageResult<>();
if (response.getCode() == 200) {
UserPageData data = response.getData();
result.setTotal(data.getTotal());
result.setTotalPages(data.getTotalPages());
result.setData(data.getList());
result.setPageNum(pageNum);
result.setPageSize(pageSize);
}
return result;
},
businessParam,
pageNum,
pageSize
);
}
}方案优势
- 代码复用性高:所有分页接口的通用逻辑都封装在
FeignPageCaller中,新增分页接口只需要编写差异化的参数绑定和结果解析逻辑 - 扩展性强:通过函数式接口可以灵活适配不同的分页规则,比如偏移量分页、游标分页都可以通过自定义解析器实现
- 逻辑清晰:分页参数处理、接口调用、结果解析的职责分离,代码结构更清晰,便于维护
注意事项
在实际使用中需要注意,Feign的版本不同,请求参数的传递方式可能有差异,比如@SpringQueryMap是Spring Cloud OpenFeign提供的注解,如果是原生Feign需要做对应调整。另外如果函数式接口需要抛出异常,需要在定义时增加异常声明,或者在调用时进行异常处理。