0

0

Java Feign分页API通用抽象与函数式优化实践

霞舞

霞舞

发布时间:2025-10-15 09:44:35

|

176人浏览过

|

来源于php中文网

原创

Java Feign分页API通用抽象与函数式优化实践

本文探讨了如何通过函数式编程思想和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免费学习笔记(深入)”;

  1. 定义不同参数数量的原始API接口:这些接口直接对应Feign客户端的原始方法签名,但只包含固定参数和分页参数。
  2. 引入通用PagingApi接口:该接口只接受分页参数(page和size),并返回分页结果。
  3. 使用静态工厂方法进行参数绑定:为PagingApi接口提供静态of方法,用于将原始API接口和其固定参数进行绑定,生成一个通用的PagingApi实例。

1. 定义不同参数数量的原始API接口

我们首先定义一些接口,它们代表了具有不同数量“固定”参数(即除了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 接口

2. 引入通用PagingApi接口及静态工厂方法

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方法实际上执行了函数式编程中的“柯里化”操作,将多参数函数转换为一系列单参数函数。

3. 简化PageableCall类

有了通用的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();
    }
}

4. 使用示例

现在,调用分页获取逻辑变得更加简洁和富有表现力:

// 假设 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编写样板代码的需求。

进一步优化与注意事项

1. 合并PagingApi与PagedCall

为了进一步简化,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。

2. 分页数据 draining 逻辑的改进

原始的drainFeignPageableCall方法使用了递归实现。虽然递归在某些场景下是优雅的,但在Java中处理这种迭代性质的任务时,通常建议使用循环(for或while),原因如下:

  • 溢出风险:深度递归可能导致StackOverflowError,尤其是在处理大量分页数据时。
  • 性能:循环通常比递归有更好的性能,因为它们避免了函数调用的开销。
  • 可读性:对于简单的迭代任务,循环往往比递归更容易理解。

推荐将递归重构为迭代循环:

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的调用抽象化,使其能够以更具描述性、更灵活的方式适应不同参数数量的场景。这种方法显著减少了样板代码,提高了代码的可维护性和扩展性。同时,将分页数据获取逻辑从递归改为迭代,能够有效避免潜在的栈溢出问题,并提升程序的性能和可读性。在实际开发中,应始终追求这种简洁、高效且易于维护的抽象模式。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1091

2023.08.02

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

108

2023.09.25

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1091

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

620

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

355

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

235

2025.08.29

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2041

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

682

2025.10.17

Python WebSocket实时通信与异步服务开发实践
Python WebSocket实时通信与异步服务开发实践

本专题聚焦 Python 在实时通信场景中的开发实践,系统讲解 WebSocket 协议原理、长连接管理、消息推送机制以及异步服务架构设计。内容包括客户端与服务端通信实现、连接稳定性优化、消息队列集成及高并发处理策略。通过完整案例,帮助开发者构建高效稳定的实时通信系统,适用于聊天应用、实时数据推送等场景。

7

2026.03.18

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.5万人学习

C# 教程
C# 教程

共94课时 | 11.6万人学习

Java 教程
Java 教程

共578课时 | 84.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号