
本文探讨了如何通过函数式编程思想和java 8+的特性,优化和抽象feign客户端中带有分页参数的api调用。通过引入泛型接口和静态工厂方法,我们能够以更简洁、更具描述性的方式处理不同参数数量的api,有效减少了重复代码,并提升了代码的灵活性和可维护性。文章还建议将递归分页调用重构为迭代方式,以提高性能和可读性。
在现代微服务架构中,Feign作为声明式HTTP客户端被广泛应用于服务间通信。当需要处理大量带有分页参数的API调用时,如何优雅地抽象这些调用,避免为每个参数数量不同的API编写重复的接口和类,成为了一个常见的挑战。最初的实现可能为每个参数类型组合定义一个特定的接口(如SingleParamPagingApi)和一个包装类(如SingleParamPageableCall),但这会导致大量的样板代码,难以维护和扩展。
假设我们有一个通用的分页数据获取逻辑,它需要一个能够执行分页API调用的接口。最初的设计可能如下:
public interface SingleParamPagingApi<T> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(String arg, int page, int size) throws RuntimeException;
}
public static class SingleParamPageableCall<T> implements PagedCall<T> {
SingleParamPagingApi<T> fun;
String param;
public SingleParamPageableCall(SingleParamPagingApi<T> fun, String param) {
this.fun = fun;
this.param = param;
}
@Override
public BaseFeignResult<T> call(int p, int s) {
BaseFeignResult.BaseFeignResultBuilder<T> builder = BaseFeignResult.builder();
try {
builder.resp(fun.callFeignApi(param, p, s));
} catch (RuntimeException e) {
builder.excp(e);
}
return builder.build();
}
}
// 核心分页调用逻辑
public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagedCall<T> feignCall) {
// ... 分页获取逻辑 ...
}这种方法的问题在于,如果存在一个API需要两个参数(例如callFeignApi(String arg1, String arg2, int page, int size)),我们就需要定义一个新的DoubleParamPagingApi接口和DoubleParamPageableCall类。这显然违背了DRY(Don't Repeat Yourself)原则,并增加了代码的复杂性。
为了解决上述问题,我们可以利用Java 8的函数式接口和方法引用特性,创建一个更具描述性和灵活性的抽象层。核心思想是:
立即学习“Java免费学习笔记(深入)”;
我们首先定义一些接口,它们代表了具有不同数量“固定”参数(即除了page和size之外的参数)的Feign API方法签名。
// 具有一个固定参数的API接口
public interface PagingApi1<T, A0> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, int page, int size) throws RuntimeException;
}
// 具有两个固定参数的API接口
public interface PagingApi2<T, A0, A1> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, A1 arg1, int page, int size) throws RuntimeException;
}
// 可以根据需要定义更多 PagingApiN 接口PagingApi是我们的核心抽象,它只关心page和size参数。关键在于其静态of方法,它们负责将具体API的固定参数“绑定”到PagingApi实例中。
public interface PagingApi<T> {
// 静态工厂方法:用于绑定一个固定参数的API
static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) {
// 返回一个lambda表达式,它捕获了arg0,并调用原始API
return (p, s) -> api.callFeignApi(arg0, p, s);
}
// 静态工厂方法:用于绑定两个固定参数的API
static <T, A0, A1> PagingApi<T> of(PagingApi2<T, A0, A1> api, A0 arg0, A1 arg1) {
// 返回一个lambda表达式,它捕获了arg0和arg1,并调用原始API
return (p, s) -> api.callFeignApi(arg0, arg1, p, s);
}
// 此方法是通用PagingApi的实际调用签名,只接受分页参数
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException;
}通过这种方式,PagingApi.of方法实际上执行了函数式编程中的“柯里化”操作,将多参数函数转换为一系列单参数函数。
有了通用的PagingApi,我们的PageableCall类就不再需要关心具体的参数数量,它只需接收一个PagingApi实例。
public static class PageableCall<T> implements PagedCall<T> {
PagingApi<T> fun; // 现在只依赖于通用的 PagingApi
public PageableCall(PagingApi<T> fun) {
this.fun = fun;
}
@Override
public BaseFeignResult<T> call(int p, int s) {
BaseFeignResult.BaseFeignResultBuilder<T> builder = BaseFeignResult.builder();
try {
builder.resp(fun.callFeignApi(p, s)); // 直接调用 PagingApi 的方法
} catch (RuntimeException e) {
builder.excp(e);
}
return builder.build();
}
}现在,调用分页获取逻辑变得更加简洁和富有表现力:
// 假设 ordersFeignClient::getOrdersBySampleIds 是一个接受 (String, int, int) 参数的方法
drainFeignPageableCall(
new PageableCall<GetOrderInfoDto>(
PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596") // 使用静态of方法绑定参数
)
);
// 如果有一个Feign方法是 (String, String, int, int)
// drainFeignPageableCall(
// new PageableCall<AnotherInfoDto>(
// PagingApi.of(anotherFeignClient::getAnotherInfo, "param1Value", "param2Value")
// )
// );这种方式极大地减少了为每个不同参数签名的API编写样板代码的需求。
为了进一步简化,PagingApi和PagedCall可以合并为一个接口,因为PagedCall的call方法与PagingApi的callFeignApi方法签名非常相似。
// 合并后的接口
public interface UnifiedPagedCaller<T> {
static <T, A0> UnifiedPagedCaller<T> of(PagingApi1<T, A0> api, A0 arg0) {
return (p, s) -> { /* 包装调用逻辑 */ };
}
// ... 其他 of 方法 ...
BaseFeignResult<T> call(int p, int s); // 封装了 ResponseEntity 和异常处理
}这样,drainFeignPageableCall可以直接接收UnifiedPagedCaller。
原始的drainFeignPageableCall方法使用了递归实现。虽然递归在某些场景下是优雅的,但在Java中处理这种迭代性质的任务时,通常建议使用循环(for或while),原因如下:
推荐将递归重构为迭代循环:
public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagedCall<T> feignCall) {
List<BaseFeignResult<T>> allResults = new ArrayList<>();
int page = 0;
int pageSize = 10; // 假设固定页面大小
while (true) {
BaseFeignResult<T> currentPageResult = feignCall.call(page, pageSize);
allResults.add(currentPageResult);
// 检查是否还有下一页数据
// 这里需要根据 IVDPagedResponseOf<T> 的具体结构判断
// 假设 IVDPagedResponseOf 有一个 getTotalElements() 和 getContent() 方法
if (currentPageResult.getResp() == null || currentPageResult.getResp().getBody() == null) {
// 如果响应为空,可能发生错误或数据结束
break;
}
IVDPagedResponseOf<T> pagedResponse = currentPageResult.getResp().getBody();
if (pagedResponse.getContent() == null || pagedResponse.getContent().size() < pageSize) {
// 如果当前页数据量小于页面大小,说明是最后一页
break;
}
page++;
}
return allResults;
}注意:上述循环中的分页结束条件pagedResponse.getContent().size() < pageSize是一个常见的判断方式,但如果最后一页恰好数据量等于pageSize,则可能导致多一次空查询。更精确的判断方式通常是依赖分页响应中提供的totalElements、totalPages或last等字段。例如,如果IVDPagedResponseOf包含isLastPage()或getTotalPages()方法,应优先使用它们。
通过引入多参数的函数式接口、一个通用的PagingApi接口及其静态工厂方法,我们成功地将Feign分页API的调用抽象化,使其能够以更具描述性、更灵活的方式适应不同参数数量的场景。这种方法显著减少了样板代码,提高了代码的可维护性和扩展性。同时,将分页数据获取逻辑从递归改为迭代,能够有效避免潜在的栈溢出问题,并提升程序的性能和可读性。在实际开发中,应始终追求这种简洁、高效且易于维护的抽象模式。
以上就是Java Feign分页API通用抽象与函数式优化实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号