0

0

使用Java函数式接口抽象分页Feign API调用

DDD

DDD

发布时间:2025-10-16 11:50:01

|

786人浏览过

|

来源于php中文网

原创

使用Java函数式接口抽象分页Feign API调用

本文探讨了如何通过java函数式接口和泛型,优雅地解决feign api分页调用中参数多样性导致的重复代码问题。通过引入统一的`pagingapi`接口和静态工厂方法,我们能够以描述性的方式绑定不同数量的参数,从而实现对各类分页api的通用化处理和数据抽取,显著减少了样板代码,并提升了代码的可维护性和可读性。

在微服务架构中,Feign作为声明式HTTP客户端,极大地简化了服务间调用。然而,当需要处理大量支持分页的Feign API时,如果这些API的入参除了分页信息(页码和大小)外还包含不同数量的其他业务参数,就可能导致大量的样板代码。例如,一个API可能只带一个业务参数,而另一个可能带两个,这使得为每个API编写通用的分页数据抽取逻辑变得复杂且冗余。

问题描述与传统方法的局限

假设我们有一个通用的分页数据抽取服务,其核心逻辑是根据给定的分页API接口,从第一页开始逐页获取数据直到所有数据被抽取完毕。最初的实现可能类似于以下结构:

// 核心抽取逻辑(简化版)
public  List> drainFeignPageableCall(PagedCall feignCall) {
    // ... 调用 feignCall.call(0, 10) 获取第一页
    // ... 递归或循环调用获取后续页面
    return null; // 实际返回所有页面的数据
}

// 抽象分页API调用的接口
public interface PagedCall {
    BaseFeignResult call(int p, int s);
}

// 针对单参数API的实现
public static class SingleParamPageableCall implements PagedCall {
    SingleParamPagingApi fun;
    String param;

    public SingleParamPageableCall(SingleParamPagingApi fun, String param) {
        this.fun = fun;
        this.param = param;
    }

    @Override
    public BaseFeignResult call(int p, int s) {
        // 包装实际的Feign调用
        return BaseFeignResult.builder()
                .resp(fun.callFeignApi(param, p, s))
                .build();
    }
}

// 针对单参数的Feign接口定义
public interface SingleParamPagingApi {
    ResponseEntity> callFeignApi(String arg, int page, int size) throws RuntimeException;
}

// 示例调用
drainFeignPageableCall(new BaseService.SingleParamPageableCall(ordersFeignClient::getOrdersBySampleIds, "34596"));

这种方法的问题在于,每当遇到一个具有不同数量业务参数的分页API时(例如,两个参数、三个参数),我们就需要为它定义一个新的接口(如TwoParamPagingApi)和一个新的PagedCall实现类(如TwoParamPageableCall),这导致了大量的样板代码和类型膨胀,难以维护。我们期望的是一种更具描述性、更函数式的实现方式,能够将参数映射到方法调用的过程抽象化,而无需定义繁重的中间对象。

使用函数式接口实现参数绑定与抽象

Java 8引入的函数式接口为解决这类问题提供了强大的工具。我们可以通过定义不同参数数量的函数式接口,并在一个统一的接口中提供静态工厂方法来绑定这些参数,从而将复杂的API签名转换为一个只接受分页参数的简单接口。

立即学习Java免费学习笔记(深入)”;

核心接口定义

首先,我们定义针对不同参数数量的原始Feign API调用的函数式接口。这里以一个参数和两个参数为例:

// 针对一个业务参数的Feign API接口
public interface PagingApi1 {
    ResponseEntity> callFeignApi(A0 arg0, int page, int size) throws RuntimeException;
}

// 针对两个业务参数的Feign API接口
public interface PagingApi2 {
    ResponseEntity> callFeignApi(A0 arg0, A1 arg1, int page, int size) throws RuntimeException;
}

接下来,定义一个统一的PagingApi接口,它只关心分页参数(页码和大小),并提供静态工厂方法来“适配”上述不同参数数量的原始API。

// 统一的分页API接口,只接受页码和大小
public interface PagingApi {
    // 静态工厂方法:绑定一个业务参数
    static  PagingApi of(PagingApi1 api, A0 arg0) {
        return (p, s) -> api.callFeignApi(arg0, p, s);
    }

    // 静态工厂方法:绑定两个业务参数
    static  PagingApi of(PagingApi2 api, A0 arg0, A1 arg1) {
        return (p, s) -> api.callFeignApi(arg0, arg1, p, s);
    }

    // 实际执行Feign API调用的方法
    ResponseEntity> callFeignApi(int page, int size) throws RuntimeException;
}

通过PagingApi.of()方法,我们可以将一个具有多个参数的Feign方法引用(如ordersFeignClient::getOrdersBySampleIds)和其固定的业务参数绑定起来,生成一个只接受页码和大小的PagingApi实例。这实现了参数的“柯里化”或部分应用。

通用PageableCall实现

有了统一的PagingApi,我们的PagedCall接口的实现也变得极其简洁:

jquery插件库合集
jquery插件库合集

pui 是一款基于jQyery开发的插件库。目前线上稳定使用已有2年多,丰富的接口,简单明了的调用方式,灵活的回调函数,让您轻轻松松打造出富有灵活交互的Web前端界面解决方案。 插件库封装了布局、表单元素、表单校验、弹窗、toast、气泡pop、tab切换、日历时间、分页、表格、树、css命名等功能

下载
// 通用的 PageableCall 实现
public static class PageableCall implements PagedCall {
    PagingApi fun; // 现在它只依赖于统一的 PagingApi

    public PageableCall(PagingApi fun) {
        this.fun = fun;
    }

    @Override
    public BaseFeignResult call(int p, int s) {
        BaseFeignResult.BaseFeignResultBuilder 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> getOrdersBySampleIds(String sampleId, int page, int size);

drainFeignPageableCall(
        new PageableCall(
                PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596")
        )
);

通过PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"),我们以函数式的方式描述了如何将"34596"这个参数绑定到getOrdersBySampleIds方法上,从而生成了一个新的PagingApi实例,这个实例在内部已经“记住”了"34596"这个参数,后续调用时只需提供页码和大小即可。

进一步优化与注意事项

  1. 接口合并: PagingApi和PagedCall在功能上非常相似,都可以进一步合并为一个接口,例如直接让PagingApi实现PagedCall的功能,或者将PagingApi作为PagedCall的唯一接口。

    // 合并 PagingApi 和 PagedCall
    public interface PagingApi {
        // 静态工厂方法不变
        static  PagingApi of(PagingApi1 api, A0 arg0) {
            return (p, s) -> api.callFeignApi(arg0, p, s);
        }
        // ... 其他 of 方法
    
        // 原始的 callFeignApi 方法,现在可以作为 PagedCall 的实现
        ResponseEntity> callFeignApi(int page, int size) throws RuntimeException;
    
        // 包装成 BaseFeignResult 的方法,可以直接在 PagingApi 内部实现
        default BaseFeignResult call(int p, int s) {
            BaseFeignResult.BaseFeignResultBuilder builder = BaseFeignResult.builder();
            try {
                builder.resp(callFeignApi(p, s));
            } catch (RuntimeException e) {
                builder.excp(e);
            }
            return builder.build();
        }
    }
    
    // 此时 drainFeignPageableCall 的签名可以变为:
    // public  List> drainFeignPageableCall(PagingApi feignCall) { ... }
    
    // 调用方式简化为:
    // drainFeignPageableCall(PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"));
  2. 避免递归调用: 在drainFeignPageableCall方法中,原始的递归实现虽然具有函数式风格,但在Java中可能导致溢出(StackOverflowError),尤其是在处理大量页面时。更健壮的做法是使用迭代(for或while循环)来替代递归。

    // 迭代实现的 drainFeignPageableCall 示例
    public  List> drainFeignPageableCall(PagingApi feignCall, int pageSize) {
        List> allResults = new ArrayList<>();
        int page = 0;
        boolean hasMore = true;
    
        while (hasMore) {
            BaseFeignResult currentPageResult = feignCall.call(page, pageSize);
            allResults.add(currentPageResult);
    
            // 假设 IVDPagedResponseOf 有一个 getTotalPages 或 getContent() 方法
            // 这里需要根据实际的 IVDPagedResponseOf 结构来判断是否还有下一页
            // 简单示例:如果当前页返回的数据量小于 pageSize,则认为没有更多数据了
            // 更准确的判断应基于 totalElements 或 totalPages
            if (currentPageResult.resp != null && currentPageResult.resp.getBody() != null) {
                List data = currentPageResult.resp.getBody().getData();
                if (data == null || data.size() < pageSize) {
                    hasMore = false;
                }
            } else {
                // 处理异常或空响应情况
                hasMore = false;
            }
            page++;
        }
        return allResults;
    }

    请注意,上述迭代逻辑中的currentPageResult.resp.getBody().getData().size()

总结

通过引入Java的函数式接口和静态工厂方法,我们能够以高度抽象和描述性的方式处理具有不同参数签名的分页Feign API。这种方法避免了为每种参数组合创建大量中间接口和实现类,显著减少了样板代码,提高了代码的可读性和可维护性。同时,结合迭代而非递归的抽取逻辑,可以构建一个既优雅又健壮的通用分页数据抽取服务。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
while的用法
while的用法

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

94

2023.09.25

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

538

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

26

2026.01.06

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

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

1100

2023.10.19

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

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

189

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1525

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

18

2026.01.19

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

31

2026.01.28

热门下载

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

精品课程

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

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.4万人学习

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

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