
本文探讨了如何通过java函数式接口和泛型,优雅地解决feign api分页调用中参数多样性导致的重复代码问题。通过引入统一的`pagingapi`接口和静态工厂方法,我们能够以描述性的方式绑定不同数量的参数,从而实现对各类分页api的通用化处理和数据抽取,显著减少了样板代码,并提升了代码的可维护性和可读性。
在微服务架构中,Feign作为声明式HTTP客户端,极大地简化了服务间调用。然而,当需要处理大量支持分页的Feign API时,如果这些API的入参除了分页信息(页码和大小)外还包含不同数量的其他业务参数,就可能导致大量的样板代码。例如,一个API可能只带一个业务参数,而另一个可能带两个,这使得为每个API编写通用的分页数据抽取逻辑变得复杂且冗余。
假设我们有一个通用的分页数据抽取服务,其核心逻辑是根据给定的分页API接口,从第一页开始逐页获取数据直到所有数据被抽取完毕。最初的实现可能类似于以下结构:
// 核心抽取逻辑(简化版)
public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagedCall<T> feignCall) {
// ... 调用 feignCall.call(0, 10) 获取第一页
// ... 递归或循环调用获取后续页面
return null; // 实际返回所有页面的数据
}
// 抽象分页API调用的接口
public interface PagedCall<T> {
BaseFeignResult<T> call(int p, int s);
}
// 针对单参数API的实现
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) {
// 包装实际的Feign调用
return BaseFeignResult.<T>builder()
.resp(fun.callFeignApi(param, p, s))
.build();
}
}
// 针对单参数的Feign接口定义
public interface SingleParamPagingApi<T> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(String arg, int page, int size) throws RuntimeException;
}
// 示例调用
drainFeignPageableCall(new BaseService.SingleParamPageableCall<GetOrderInfoDto>(ordersFeignClient::getOrdersBySampleIds, "34596"));这种方法的问题在于,每当遇到一个具有不同数量业务参数的分页API时(例如,两个参数、三个参数),我们就需要为它定义一个新的接口(如TwoParamPagingApi)和一个新的PagedCall实现类(如TwoParamPageableCall),这导致了大量的样板代码和类型膨胀,难以维护。我们期望的是一种更具描述性、更函数式的实现方式,能够将参数映射到方法调用的过程抽象化,而无需定义繁重的中间对象。
Java 8引入的函数式接口为解决这类问题提供了强大的工具。我们可以通过定义不同参数数量的函数式接口,并在一个统一的接口中提供静态工厂方法来绑定这些参数,从而将复杂的API签名转换为一个只接受分页参数的简单接口。
立即学习“Java免费学习笔记(深入)”;
首先,我们定义针对不同参数数量的原始Feign API调用的函数式接口。这里以一个参数和两个参数为例:
// 针对一个业务参数的Feign API接口
public interface PagingApi1<T, A0> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, int page, int size) throws RuntimeException;
}
// 针对两个业务参数的Feign API接口
public interface PagingApi2<T, A0, A1> {
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, A1 arg1, int page, int size) throws RuntimeException;
}接下来,定义一个统一的PagingApi接口,它只关心分页参数(页码和大小),并提供静态工厂方法来“适配”上述不同参数数量的原始API。
// 统一的分页API接口,只接受页码和大小
public interface PagingApi<T> {
// 静态工厂方法:绑定一个业务参数
static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) {
return (p, s) -> api.callFeignApi(arg0, p, s);
}
// 静态工厂方法:绑定两个业务参数
static <T, A0, A1> PagingApi<T> of(PagingApi2<T, A0, A1> api, A0 arg0, A1 arg1) {
return (p, s) -> api.callFeignApi(arg0, arg1, p, s);
}
// 实际执行Feign API调用的方法
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException;
}通过PagingApi.of()方法,我们可以将一个具有多个参数的Feign方法引用(如ordersFeignClient::getOrdersBySampleIds)和其固定的业务参数绑定起来,生成一个只接受页码和大小的PagingApi实例。这实现了参数的“柯里化”或部分应用。
有了统一的PagingApi,我们的PagedCall接口的实现也变得极其简洁:
// 通用的 PageableCall 实现
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)); // 直接调用统一接口
} catch (RuntimeException e) {
builder.excp(e);
}
return builder.build();
}
}这里的BaseFeignResult和IVDPagedResponseOf是根据原始问题上下文假设的数据结构,用于封装Feign调用的响应和异常。
现在,调用通用的分页数据抽取服务变得非常简洁和描述性:
// 假设 ordersFeignClient.getOrdersBySampleIds 方法签名是:
// ResponseEntity<IVDPagedResponseOf<GetOrderInfoDto>> getOrdersBySampleIds(String sampleId, int page, int size);
drainFeignPageableCall(
new PageableCall<GetOrderInfoDto>(
PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596")
)
);通过PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"),我们以函数式的方式描述了如何将"34596"这个参数绑定到getOrdersBySampleIds方法上,从而生成了一个新的PagingApi实例,这个实例在内部已经“记住”了"34596"这个参数,后续调用时只需提供页码和大小即可。
接口合并: PagingApi和PagedCall在功能上非常相似,都可以进一步合并为一个接口,例如直接让PagingApi实现PagedCall的功能,或者将PagingApi作为PagedCall的唯一接口。
// 合并 PagingApi 和 PagedCall
public interface PagingApi<T> {
// 静态工厂方法不变
static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) {
return (p, s) -> api.callFeignApi(arg0, p, s);
}
// ... 其他 of 方法
// 原始的 callFeignApi 方法,现在可以作为 PagedCall 的实现
ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException;
// 包装成 BaseFeignResult 的方法,可以直接在 PagingApi 内部实现
default BaseFeignResult<T> call(int p, int s) {
BaseFeignResult.BaseFeignResultBuilder<T> builder = BaseFeignResult.builder();
try {
builder.resp(callFeignApi(p, s));
} catch (RuntimeException e) {
builder.excp(e);
}
return builder.build();
}
}
// 此时 drainFeignPageableCall 的签名可以变为:
// public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagingApi<T> feignCall) { ... }
// 调用方式简化为:
// drainFeignPageableCall(PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"));避免递归调用: 在drainFeignPageableCall方法中,原始的递归实现虽然具有函数式风格,但在Java中可能导致栈溢出(StackOverflowError),尤其是在处理大量页面时。更健壮的做法是使用迭代(for或while循环)来替代递归。
// 迭代实现的 drainFeignPageableCall 示例
public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagingApi<T> feignCall, int pageSize) {
List<BaseFeignResult<T>> allResults = new ArrayList<>();
int page = 0;
boolean hasMore = true;
while (hasMore) {
BaseFeignResult<T> currentPageResult = feignCall.call(page, pageSize);
allResults.add(currentPageResult);
// 假设 IVDPagedResponseOf 有一个 getTotalPages 或 getContent() 方法
// 这里需要根据实际的 IVDPagedResponseOf 结构来判断是否还有下一页
// 简单示例:如果当前页返回的数据量小于 pageSize,则认为没有更多数据了
// 更准确的判断应基于 totalElements 或 totalPages
if (currentPageResult.resp != null && currentPageResult.resp.getBody() != null) {
List<T> data = currentPageResult.resp.getBody().getData();
if (data == null || data.size() < pageSize) {
hasMore = false;
}
} else {
// 处理异常或空响应情况
hasMore = false;
}
page++;
}
return allResults;
}请注意,上述迭代逻辑中的currentPageResult.resp.getBody().getData().size() < pageSize是一个简化的判断逻辑。在实际应用中,更准确的做法是依赖分页响应体中提供的totalElements、totalPages或last等字段来判断是否还有更多数据。
通过引入Java的函数式接口和静态工厂方法,我们能够以高度抽象和描述性的方式处理具有不同参数签名的分页Feign API。这种方法避免了为每种参数组合创建大量中间接口和实现类,显著减少了样板代码,提高了代码的可读性和可维护性。同时,结合迭代而非递归的抽取逻辑,可以构建一个既优雅又健壮的通用分页数据抽取服务。
以上就是使用Java函数式接口抽象分页Feign API调用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号