本文探討了如何通過(guò)java函數(shù)式接口和泛型,優(yōu)雅地解決feign api分頁(yè)調(diào)用中參數(shù)多樣性導(dǎo)致的重復(fù)代碼問(wèn)題。通過(guò)引入統(tǒng)一的`pagingapi`接口和靜態(tài)工廠方法,我們能夠以描述性的方式綁定不同數(shù)量的參數(shù),從而實(shí)現(xiàn)對(duì)各類分頁(yè)api的通用化處理和數(shù)據(jù)抽取,顯著減少了樣板代碼,并提升了代碼的可維護(hù)性和可讀性。
在微服務(wù)架構(gòu)中,F(xiàn)eign作為聲明式HTTP客戶端,極大地簡(jiǎn)化了服務(wù)間調(diào)用。然而,當(dāng)需要處理大量支持分頁(yè)的Feign API時(shí),如果這些API的入?yún)⒊朔猪?yè)信息(頁(yè)碼和大?。┩膺€包含不同數(shù)量的其他業(yè)務(wù)參數(shù),就可能導(dǎo)致大量的樣板代碼。例如,一個(gè)API可能只帶一個(gè)業(yè)務(wù)參數(shù),而另一個(gè)可能帶兩個(gè),這使得為每個(gè)API編寫通用的分頁(yè)數(shù)據(jù)抽取邏輯變得復(fù)雜且冗余。
假設(shè)我們有一個(gè)通用的分頁(yè)數(shù)據(jù)抽取服務(wù),其核心邏輯是根據(jù)給定的分頁(yè)API接口,從第一頁(yè)開始逐頁(yè)獲取數(shù)據(jù)直到所有數(shù)據(jù)被抽取完畢。最初的實(shí)現(xiàn)可能類似于以下結(jié)構(gòu):
// 核心抽取邏輯(簡(jiǎn)化版) public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagedCall<T> feignCall) { // ... 調(diào)用 feignCall.call(0, 10) 獲取第一頁(yè) // ... 遞歸或循環(huán)調(diào)用獲取后續(xù)頁(yè)面 return null; // 實(shí)際返回所有頁(yè)面的數(shù)據(jù) } // 抽象分頁(yè)API調(diào)用的接口 public interface PagedCall<T> { BaseFeignResult<T> call(int p, int s); } // 針對(duì)單參數(shù)API的實(shí)現(xiàn) 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) { // 包裝實(shí)際的Feign調(diào)用 return BaseFeignResult.<T>builder() .resp(fun.callFeignApi(param, p, s)) .build(); } } // 針對(duì)單參數(shù)的Feign接口定義 public interface SingleParamPagingApi<T> { ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(String arg, int page, int size) throws RuntimeException; } // 示例調(diào)用 drainFeignPageableCall(new BaseService.SingleParamPageableCall<GetOrderInfoDto>(ordersFeignClient::getOrdersBySampleIds, "34596"));
這種方法的問(wèn)題在于,每當(dāng)遇到一個(gè)具有不同數(shù)量業(yè)務(wù)參數(shù)的分頁(yè)API時(shí)(例如,兩個(gè)參數(shù)、三個(gè)參數(shù)),我們就需要為它定義一個(gè)新的接口(如TwoParamPagingApi)和一個(gè)新的PagedCall實(shí)現(xiàn)類(如TwoParamPageableCall),這導(dǎo)致了大量的樣板代碼和類型膨脹,難以維護(hù)。我們期望的是一種更具描述性、更函數(shù)式的實(shí)現(xiàn)方式,能夠?qū)?shù)映射到方法調(diào)用的過(guò)程抽象化,而無(wú)需定義繁重的中間對(duì)象。
Java 8引入的函數(shù)式接口為解決這類問(wèn)題提供了強(qiáng)大的工具。我們可以通過(guò)定義不同參數(shù)數(shù)量的函數(shù)式接口,并在一個(gè)統(tǒng)一的接口中提供靜態(tài)工廠方法來(lái)綁定這些參數(shù),從而將復(fù)雜的API簽名轉(zhuǎn)換為一個(gè)只接受分頁(yè)參數(shù)的簡(jiǎn)單接口。
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
首先,我們定義針對(duì)不同參數(shù)數(shù)量的原始Feign API調(diào)用的函數(shù)式接口。這里以一個(gè)參數(shù)和兩個(gè)參數(shù)為例:
// 針對(duì)一個(gè)業(yè)務(wù)參數(shù)的Feign API接口 public interface PagingApi1<T, A0> { ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, int page, int size) throws RuntimeException; } // 針對(duì)兩個(gè)業(yè)務(wù)參數(shù)的Feign API接口 public interface PagingApi2<T, A0, A1> { ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, A1 arg1, int page, int size) throws RuntimeException; }
接下來(lái),定義一個(gè)統(tǒng)一的PagingApi接口,它只關(guān)心分頁(yè)參數(shù)(頁(yè)碼和大小),并提供靜態(tài)工廠方法來(lái)“適配”上述不同參數(shù)數(shù)量的原始API。
// 統(tǒng)一的分頁(yè)API接口,只接受頁(yè)碼和大小 public interface PagingApi<T> { // 靜態(tài)工廠方法:綁定一個(gè)業(yè)務(wù)參數(shù) static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) { return (p, s) -> api.callFeignApi(arg0, p, s); } // 靜態(tài)工廠方法:綁定兩個(gè)業(yè)務(wù)參數(shù) 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); } // 實(shí)際執(zhí)行Feign API調(diào)用的方法 ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException; }
通過(guò)PagingApi.of()方法,我們可以將一個(gè)具有多個(gè)參數(shù)的Feign方法引用(如ordersFeignClient::getOrdersBySampleIds)和其固定的業(yè)務(wù)參數(shù)綁定起來(lái),生成一個(gè)只接受頁(yè)碼和大小的PagingApi實(shí)例。這實(shí)現(xiàn)了參數(shù)的“柯里化”或部分應(yīng)用。
有了統(tǒng)一的PagingApi,我們的PagedCall接口的實(shí)現(xiàn)也變得極其簡(jiǎn)潔:
// 通用的 PageableCall 實(shí)現(xiàn) public static class PageableCall<T> implements PagedCall<T> { PagingApi<T> fun; // 現(xiàn)在它只依賴于統(tǒng)一的 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)); // 直接調(diào)用統(tǒng)一接口 } catch (RuntimeException e) { builder.excp(e); } return builder.build(); } }
這里的BaseFeignResult和IVDPagedResponseOf是根據(jù)原始問(wèn)題上下文假設(shè)的數(shù)據(jù)結(jié)構(gòu),用于封裝Feign調(diào)用的響應(yīng)和異常。
現(xiàn)在,調(diào)用通用的分頁(yè)數(shù)據(jù)抽取服務(wù)變得非常簡(jiǎn)潔和描述性:
// 假設(shè) ordersFeignClient.getOrdersBySampleIds 方法簽名是: // ResponseEntity<IVDPagedResponseOf<GetOrderInfoDto>> getOrdersBySampleIds(String sampleId, int page, int size); drainFeignPageableCall( new PageableCall<GetOrderInfoDto>( PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596") ) );
通過(guò)PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"),我們以函數(shù)式的方式描述了如何將"34596"這個(gè)參數(shù)綁定到getOrdersBySampleIds方法上,從而生成了一個(gè)新的PagingApi實(shí)例,這個(gè)實(shí)例在內(nèi)部已經(jīng)“記住”了"34596"這個(gè)參數(shù),后續(xù)調(diào)用時(shí)只需提供頁(yè)碼和大小即可。
接口合并: PagingApi和PagedCall在功能上非常相似,都可以進(jìn)一步合并為一個(gè)接口,例如直接讓PagingApi實(shí)現(xiàn)PagedCall的功能,或者將PagingApi作為PagedCall的唯一接口。
// 合并 PagingApi 和 PagedCall public interface PagingApi<T> { // 靜態(tài)工廠方法不變 static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) { return (p, s) -> api.callFeignApi(arg0, p, s); } // ... 其他 of 方法 // 原始的 callFeignApi 方法,現(xiàn)在可以作為 PagedCall 的實(shí)現(xiàn) ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException; // 包裝成 BaseFeignResult 的方法,可以直接在 PagingApi 內(nèi)部實(shí)現(xiàn) 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(); } } // 此時(shí) drainFeignPageableCall 的簽名可以變?yōu)椋?// public <T> List<BaseFeignResult<T>> drainFeignPageableCall(PagingApi<T> feignCall) { ... } // 調(diào)用方式簡(jiǎn)化為: // drainFeignPageableCall(PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596"));
避免遞歸調(diào)用: 在drainFeignPageableCall方法中,原始的遞歸實(shí)現(xiàn)雖然具有函數(shù)式風(fēng)格,但在Java中可能導(dǎo)致棧溢出(StackOverflowError),尤其是在處理大量頁(yè)面時(shí)。更健壯的做法是使用迭代(for或while循環(huán))來(lái)替代遞歸。
// 迭代實(shí)現(xiàn)的 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); // 假設(shè) IVDPagedResponseOf 有一個(gè) getTotalPages 或 getContent() 方法 // 這里需要根據(jù)實(shí)際的 IVDPagedResponseOf 結(jié)構(gòu)來(lái)判斷是否還有下一頁(yè) // 簡(jiǎn)單示例:如果當(dāng)前頁(yè)返回的數(shù)據(jù)量小于 pageSize,則認(rèn)為沒有更多數(shù)據(jù)了 // 更準(zhǔn)確的判斷應(yīng)基于 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 { // 處理異常或空響應(yīng)情況 hasMore = false; } page++; } return allResults; }
請(qǐng)注意,上述迭代邏輯中的currentPageResult.resp.getBody().getData().size() < pageSize是一個(gè)簡(jiǎn)化的判斷邏輯。在實(shí)際應(yīng)用中,更準(zhǔn)確的做法是依賴分頁(yè)響應(yīng)體中提供的totalElements、totalPages或last等字段來(lái)判斷是否還有更多數(shù)據(jù)。
通過(guò)引入Java的函數(shù)式接口和靜態(tài)工廠方法,我們能夠以高度抽象和描述性的方式處理具有不同參數(shù)簽名的分頁(yè)Feign API。這種方法避免了為每種參數(shù)組合創(chuàng)建大量中間接口和實(shí)現(xiàn)類,顯著減少了樣板代碼,提高了代碼的可讀性和可維護(hù)性。同時(shí),結(jié)合迭代而非遞歸的抽取邏輯,可以構(gòu)建一個(gè)既優(yōu)雅又健壯的通用分頁(yè)數(shù)據(jù)抽取服務(wù)。
以上就是使用Java函數(shù)式接口抽象分頁(yè)Feign API調(diào)用的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!
每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)