重写Feign编码器
背景:
有个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修饰的字段才进行下划线转换。