背景:
有個spring cloud 架構的項目需要調用php小組的api接口,但php提供的接口入參大部分是下划線命名,而Java這邊的實體類是按照駝峰編寫,如果使用Fegin調用會導致php無法接收參數,例如userId傳過去后,由於php那邊是user_id,這樣php接口無法識別,所以針對這個問題進行了如下特殊處理,主要是通過重寫fegin的默認編碼器實現
編碼器原理
Spring Cloud Feign 的編碼器、解碼器和客戶端都是支持自定義擴展,可以對請求以及結果和發起請求的過程進行自定義實現,Feign 默認encoder實現是SpringEncoder,默認decocer實現是ResponseEntityDecoder,另外還有一些其它的編解碼器。
Encoder/ Decoder 實現 | 說明 |
---|---|
JacksonEncoder,JacksonDecoder | 基於 Jackson 格式的持久化轉換協議 |
GsonEncoder,GsonDecoder | 基於Google GSON 格式的持久化轉換協議 |
SaxEncoder,SaxDecoder | 基於XML 格式的Sax 庫持久化轉換協議 |
JAXBEncoder,JAXBDecoder | 基於XML 格式的JAXB 庫持久化轉換協議 |
ResponseEntityEncoder,ResponseEntityDecoder | Spring MVC 基於 ResponseEntity< T > 返回格式的轉換協議 |
SpringEncoder,SpringDecoder | 基於Spring MVC HttpMessageConverters 一套機制實現的轉換協議 ,應用於Spring Cloud 體系中 |
實現方案
重寫SpringEncoder
** * 由於下游接口php是下划線命名,java是駝峰,通過fegin調用會導致下游php接收不到,所以需要針對這種問題特殊處理 */ public class FeignClientEncoder extends SpringEncoder { public static final char UNDERLINE = '_'; public FeignClientEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { super(messageConverters); } @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { try { super.encode(object, bodyType, template); if(ObjectUtils.isNotEmpty(AnnotationUtils.getAnnotation(object.getClass(), FeginCamelCase.class))){ final String s = JSON.toJSONString(object); final Object jsonObject = JSON.parse(s); Class clazz = Class.forName(bodyType.getTypeName()); convertToCamelCase(jsonObject,clazz); template.body(JSON.toJSONString(jsonObject)); } } catch (Exception ex) { ex.printStackTrace(); } } public final static void convertToCamelCase(Object json,Class clazz) throws NoSuchFieldException { if (json instanceof JSONArray) { JSONArray arr = (JSONArray) json; for (Object obj : arr) { convertToCamelCase(obj,clazz); } } else if (json instanceof JSONObject) { JSONObject jo = (JSONObject) json; Set<String> keys = jo.keySet(); String[] array = keys.toArray(new String[keys.size()]); for (String key : array) { Field field = clazz.getField(key); //由於有些字段因為特殊原因,不想處理成下划線形式,這時候就可以通過注解的方式進行標記不進行處理 if(ObjectUtils.isEmpty(AnnotationUtils.getAnnotation(field,IgnoreFeginCamelCase.class))){ Object value = jo.get(key); final String underLineKey = camelToUnderline(key,1); jo.remove(key); jo.put(underLineKey, value); convertToCamelCase(value,clazz); } } } } //駝峰轉下划線 public static String camelToUnderline(String param, Integer charType) { if (param == null || "".equals(param.trim())) { return ""; } int len = param.length(); StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = param.charAt(i); if (Character.isUpperCase(c)) { sb.append(UNDERLINE); } if (charType == 2) { sb.append(Character.toUpperCase(c)); //統一都轉大寫 } else { sb.append(Character.toLowerCase(c)); //統一都轉小寫 } } return sb.toString(); }
啟動類指定編碼器
@EnableFeignClients(defaultConfiguration = FeignClientEncoder.class)
最后
指定編碼器后,所有經過Fegin請求都會走這個編碼器,這樣會導致原有的業務受到影響,為了不影響原有的業務,需要通過標識來區分到底什么字段能轉下划線,所以在上面的代碼里,我寫了一個IgnoreFeginCamelCase注解,用於判斷被IgnoreFeginCamelCase修飾的字段才進行下划線轉換。