Spring?Cloud?Feign?使用對(duì)象參數(shù)的操作
概述
Spring Cloud Feign 用于微服務(wù)的封裝,通過(guò)接口代理的實(shí)現(xiàn)方式讓微服務(wù)調(diào)用變得簡(jiǎn)單,讓微服務(wù)的使用上如同本地服務(wù)。但是它在傳參方面不是很完美。在使用 Feign 代理 GET 請(qǐng)求時(shí),對(duì)于簡(jiǎn)單參數(shù)(基本類(lèi)型、包裝器、字符串)的使用上沒(méi)有困難,但是在使用對(duì)象傳參時(shí)卻無(wú)法自動(dòng)的將對(duì)象包含的字段解析出來(lái)。
如果你沒(méi)耐心看完,直接跳到最后一個(gè)標(biāo)題跟著操作就行了。
@RequestBody
對(duì)象傳參是很常見(jiàn)的操作,雖然可以通過(guò)一個(gè)個(gè)參數(shù)傳遞來(lái)替代,但是那樣就太麻煩了,所以必須解決這個(gè)問(wèn)題。
我在網(wǎng)上看到有人用 @RequestBody 來(lái)注解對(duì)象參數(shù),我在嘗試后發(fā)現(xiàn)確實(shí)可用。這個(gè)方案實(shí)際使用 body 體裝了參數(shù)(使用的是 GET 請(qǐng)求),但是這個(gè)方案有些問(wèn)題:
- 注解需要在 consumer 和 provider 兩邊都有,這造成了麻煩
- 使用接口測(cè)試工具 Postman 無(wú)法跑通微服務(wù),后來(lái)發(fā)現(xiàn)是因?yàn)?body 體的格式選擇不正確,這個(gè)格式不是通常的表單或者路徑拼接,而是 GraphQL。我沒(méi)有研究過(guò)這種格式應(yīng)該如何填寫(xiě)參數(shù),但是 Postman 上并沒(méi)有給出像表單那樣方便的格式,這對(duì)于測(cè)試是很不利的。
@SpringQueryMap
于是我繼續(xù)尋找答案,發(fā)現(xiàn)可以使用 @SpringQueryMap 僅添加在 consumer 的參數(shù)上就能自動(dòng)對(duì) Map 類(lèi)型參數(shù)編碼再拼接到 URL 上。而我用的高版本的 Feign,可以直接把對(duì)象編碼。
可是正當(dāng)我以為得到正解時(shí),卻發(fā)現(xiàn)還是有問(wèn)題:
我明明在 Date 類(lèi)型的字段上加上了 @DateTimeFormat(pattern = "yyyy-MM-dd"),卻沒(méi)有生效,他用自己的方式進(jìn)行了編碼(或者說(shuō)序列化),而且官方確實(shí)沒(méi)有提供這種格式化方式。
又一番找尋后發(fā)現(xiàn)了一位大佬自己實(shí)現(xiàn)了一個(gè)注解轉(zhuǎn)換替代 @SpringQueryMap,并實(shí)現(xiàn)了豐富的格式化功能 ORZ(原文鏈接:Spring Cloud Feign實(shí)現(xiàn)自定義復(fù)雜對(duì)象傳參),只能說(shuō)佩服佩服。但是我沒(méi)有那樣的技術(shù),又不太想復(fù)制粘貼他那一大堆的代碼,因?yàn)槌隽藛?wèn)題也不好改,所以我還是想堅(jiān)持最大限度地使用框架,最小限度的給框架填坑。
QueryMapEncoder
終于功夫不費(fèi)有心人,我發(fā)現(xiàn)了 Feign 預(yù)留的自定義編碼器接口 QueryMapEncoder,框架提供了兩個(gè)實(shí)現(xiàn):
- FieldQueryMapEncoder
- BeanQueryMapEncoder
雖然這兩個(gè)實(shí)現(xiàn)不能滿(mǎn)足我的要求,但是只要稍加修改寫(xiě)一個(gè)自己的實(shí)現(xiàn)類(lèi)就行了,于是我在 FieldQueryMapEncoder 的基礎(chǔ)上修改,僅僅添加了一個(gè)方法,小改了一個(gè)方法就實(shí)現(xiàn)了功能。
原理:Feign 其實(shí)還是用 Map<String, Object> 進(jìn)行的編碼,編碼方式也很簡(jiǎn)單,String 是 key,Object 是 value。最開(kāi)始的方式就是用 Object 的 toString() 方法把參數(shù)編碼,這也是為什么 Date 字段會(huì)變成一個(gè)默認(rèn)的時(shí)間格式,因?yàn)?toString() 根本和 @DateTimeFormat 沒(méi)有關(guān)系。而高版本使用編碼器實(shí)現(xiàn)了對(duì)象傳參,實(shí)際實(shí)際上是通過(guò)簡(jiǎn)單的反射獲取對(duì)象的元數(shù)據(jù),再放到 Map 中。
上面的原理都能從 @DateTimeFormat 的注釋和編碼器的源碼中得到答案。
我們要做的就是自定義一個(gè)編碼器,實(shí)現(xiàn)在元數(shù)據(jù)放入 Map 之前根據(jù)需要把字段變成我們想要的字符串。下面是我實(shí)現(xiàn)的代碼,供參考:
package com.example.billmanagerfront.config.encoder;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.format.annotation.DateTimeFormat;
import feign.Param;
import feign.QueryMapEncoder;
import feign.codec.EncodeException;
public class PowerfulQueryMapEncoder implements QueryMapEncoder {
private final Map<Class<?>, ObjectParamMetadata> classToMetadata = new ConcurrentHashMap<>();
@Override
public Map<String, Object> encode(Object object) throws EncodeException {
ObjectParamMetadata metadata = classToMetadata.computeIfAbsent(object.getClass(),
ObjectParamMetadata::parseObjectType);
return metadata.objectFields.stream()
.map(field -> this.FieldValuePair(object, field))
.filter(fieldObjectPair -> fieldObjectPair.right.isPresent())
.collect(Collectors.toMap(this::fieldName, this::fieldObject));
}
private String fieldName(Pair<Field, Optional<Object>> pair) {
Param alias = pair.left.getAnnotation(Param.class);
return alias != null ? alias.value() : pair.left.getName();
// 可擴(kuò)展為策略模式,支持更多的格式轉(zhuǎn)換
private Object fieldObject(Pair<Field, Optional<Object>> pair) {
Object fieldObject = pair.right.get();
DateTimeFormat dateTimeFormat = pair.left.getAnnotation(DateTimeFormat.class);
if (dateTimeFormat != null) {
DateFormat format = new SimpleDateFormat(dateTimeFormat.pattern());
format.setTimeZone(TimeZone.getTimeZone("GMT+8")); // TODO: 最好不要寫(xiě)死時(shí)區(qū)
fieldObject = format.format(fieldObject);
} else {
}
return fieldObject;
private Pair<Field, Optional<Object>> FieldValuePair(Object object, Field field) {
try {
return Pair.pair(field, Optional.ofNullable(field.get(object)));
} catch (IllegalAccessException e) {
throw new EncodeException("Failure encoding object into query map", e);
private static class ObjectParamMetadata {
private final List<Field> objectFields;
private ObjectParamMetadata(List<Field> objectFields) {
this.objectFields = Collections.unmodifiableList(objectFields);
private static ObjectParamMetadata parseObjectType(Class<?> type) {
List<Field> allFields = new ArrayList<Field>();
for (Class<?> currentClass = type; currentClass != null; currentClass = currentClass.getSuperclass()) {
Collections.addAll(allFields, currentClass.getDeclaredFields());
}
return new ObjectParamMetadata(allFields.stream()
.filter(field -> !field.isSynthetic())
.peek(field -> field.setAccessible(true))
.collect(Collectors.toList()));
private static class Pair<T, U> {
private Pair(T left, U right) {
this.right = right;
this.left = left;
public final T left;
public final U right;
public static <T, U> Pair<T, U> pair(T left, U right) {
return new Pair<>(left, right);
}加注釋的方法,就是我后添加進(jìn)去的。encode 方法的最后一行稍微修改了一下,引用了我加的方法,其他都是直接借鑒過(guò)來(lái)的(本來(lái)我想更偷懶,直接繼承一下子,但是它用了私有的內(nèi)部類(lèi)導(dǎo)致我只能全部復(fù)制粘貼了)。
解決方案
1.不用引入其他的 Feign 依賴(lài),保證有下面這個(gè)就行(看網(wǎng)上其他方法還要引入特定依賴(lài),要對(duì)應(yīng)版本號(hào),挺麻煩的)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>2.編寫(xiě)上面那樣的類(lèi),你可以直接復(fù)制過(guò)去改個(gè)包名就行,如果還需要除了 Date 以外的格式化,請(qǐng)看注釋和文章分析。其中我對(duì)日期的格式化,直接使用了 @DateTimeFormat 提供的模式,和 Spring 保持了一致。
3.編寫(xiě)一個(gè) Feign 配置類(lèi),將剛自定義的編碼器注冊(cè)進(jìn)去。細(xì)節(jié)我就不多說(shuō)了:
package com.example.billmanagerfront.config;
import com.example.billmanagerfront.config.encoder.PowerfulQueryMapEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Feign;
import feign.Retryer;
@Configuration
public class FeignConfig {
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.queryMapEncoder(new PowerfulQueryMapEncoder())
.retryer(Retryer.NEVER_RETRY);
}
}4.Feign 代理接口中聲明使用這個(gè)配置類(lèi),細(xì)節(jié)不談
package com.example.billmanagerfront.client;
import java.util.List;
import com.example.billmanagerfront.config.FeignConfig;
import com.example.billmanagerfront.pojo.Bill;
import com.example.billmanagerfront.pojo.BillType;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "BILL-MANAGER", path = "bill", configuration = FeignConfig.class)
public interface BillClient {
@GetMapping("list")
List<Bill> list(@SpringQueryMap(true) Bill b);
@GetMapping("type")
List<BillType> type();
@DeleteMapping("delete/{id}")
public String delete(@PathVariable("id") Long id);
}到此這篇關(guān)于Spring Cloud Feign 如何使用對(duì)象參數(shù)的文章就介紹到這了,更多相關(guān)Spring Cloud Feign對(duì)象參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springcloud使用feign調(diào)用服務(wù)時(shí)參數(shù)內(nèi)容過(guò)大問(wèn)題
- SpringCloud之@FeignClient()注解的使用方式
- SpringCloud Open feign 使用okhttp 優(yōu)化詳解
- 詳解SpringCloud-OpenFeign組件的使用
- SpringCloud使用Feign實(shí)現(xiàn)服務(wù)調(diào)用
- 使用Spring Cloud Feign作為HTTP客戶(hù)端調(diào)用遠(yuǎn)程HTTP服務(wù)的方法(推薦)
- Spring Cloud Feign的使用案例詳解
相關(guān)文章
Java String 和 new String()的比較與區(qū)別
這篇文章主要介紹了Java String 和 new String()的區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-04-04
Java深入學(xué)習(xí)圖形用戶(hù)界面GUI之事件處理
這篇文章主要介紹了基于Java GUI 事件處理方式,一個(gè)圖形界面制作完成了,在程序開(kāi)發(fā)中只是完成了起步的工作。要想讓一個(gè)組件都發(fā)揮自己的作用.就必須對(duì)所有的組件進(jìn)行事件處理2022-05-05
線(xiàn)程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問(wèn)題排查解決
這篇文章主要為大家介紹了線(xiàn)程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問(wèn)題排查解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
JavaWeb簡(jiǎn)單用戶(hù)登錄注冊(cè)實(shí)例代碼(有驗(yàn)證碼)
這篇文章主要介紹了JavaWeb簡(jiǎn)單用戶(hù)登錄注冊(cè)實(shí)例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java操作mongodb增刪改查的基本操作實(shí)戰(zhàn)指南
MongoDB是一個(gè)基于分布式文件存儲(chǔ)的數(shù)據(jù)庫(kù),由c++語(yǔ)言編寫(xiě),旨在為WEB應(yīng)用提供可擴(kuò)展的高性能數(shù)據(jù)存儲(chǔ)解決方案,下面這篇文章主要給大家介紹了關(guān)于Java操作mongodb增刪改查的基本操作實(shí)戰(zhàn)指南,需要的朋友可以參考下2023-05-05
SpringBoot 快速實(shí)現(xiàn) api 加密的方法
在項(xiàng)目中,為了保證數(shù)據(jù)的安全,我們常常會(huì)對(duì)傳遞的數(shù)據(jù)進(jìn)行加密,常用的加密算法包括對(duì)稱(chēng)加密(AES)和非對(duì)稱(chēng)加密(RSA),本文給大家介紹SpringBoot 快速實(shí)現(xiàn) api 加密,感興趣的朋友一起看看吧2023-10-10
Java使用JSqlParser解析SQL語(yǔ)句應(yīng)用場(chǎng)景
JSqlParser是一個(gè)功能全面的Java庫(kù),用于解析SQL語(yǔ)句,支持多種SQL方言,它可以輕松集成到Java項(xiàng)目中,并提供靈活的操作方式,本文介紹Java使用JSqlParser解析SQL語(yǔ)句總結(jié),感興趣的朋友一起看看吧2024-09-09
IDEA離線(xiàn)安裝maven helper插件的圖文教程
本文通過(guò)圖文并茂的形式給大家介紹IDEA離線(xiàn)安裝maven helper插件,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-08-08

