现在都是基于Spring Cloud Feign进行微服务的调用,并且序列化的过程都封装完成的。只是自己可以定制序列化的方式,但是为了调用的时候能方便的找到问题所在等,基本都会使用json(Jackson等)方式的序列化【虽然性能比较差】。但是最近在项目上使用的时候,自己的需求是根据不同的类型(或者枚举),入参和出参会传入不同的子类,但是在接口的定义上只能使用父类进行接收。当反序列化完成后,在Controller层拿到的数据就只有父类公共的字段,而子类特有的字段在序列化时直接进行丢弃了。
当然我完全可以用一个比较宽泛的类,放入比较全的字段,只是不同类型,有的字段可能为空,只是自己完全不想那样处理,但是处理的时候发现还是费了好多力气,于是简单记录一下定制序列化和反序列化的过程。
调用的接口:
@PostMapping("checkChain")
public CheckResultDTO checkChain(@RequestBody CheckChainDTO... checkChains);
入参对象:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonDeserialize(using = CheckChainDTOJsonDeserializer.class)
public class CheckChainDTO {
/**
* 验证类型
*/
private CheckTypeEnum checkTypeEnum;
/**
* 入参
*/
private CheckRequestDTO checkRequestDTO;
}
具体不同的CheckTypeEnum,会对应自己的CheckRequestDTO子类,比如(如果不自己处理的话,Spring Cloud的反序列化完成后,就会丢弃CheckRequestSkuDTO自己特有的属性skuCodeList):
public class CheckRequestSkuDTO extends CheckRequestDTO {
/** sku编码集合 */
private List<SkuInfoDTO> skuCodeList;
}
当然出参也是一样的,所以我是在提供feign的maven包时也提供了反序列化的过程,则进行忽略,只是两个过程都需要反序列化定制。
首先,先看一下项目上使用的设置Jackson为项目的序列化、反序列化方式,相信在网上也能copy到很多。如下:
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {
private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
private static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
@Autowired
private HttpMessageConverters httpMessageConverters;
/**
* MappingJackson2HttpMessageConverter 实现了HttpMessageConverter 接口,
* httpMessageConverters.getConverters() 返回的对象里包含了MappingJackson2HttpMessageConverter
*
* @return
*/
@Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(new JacksonMapper());
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.addAll(httpMessageConverters.getConverters());
}
public static class JacksonMapper extends ObjectMapper {
public JacksonMapper() {
super();
this.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
this.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true);
this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
SimpleModule simpleModule = new SimpleModule();
//Long 类型转成 String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
simpleModule.addSerializer(long.class, ToStringSerializer.instance);
//日期格式化
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
simpleModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
simpleModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
simpleModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
simpleModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
// 定制自己的序列化方式
simpleModule.addDeserializer(CheckChainDTO.class, new CheckChainDTOJsonDeserializer());
registerModule(simpleModule);
}
}
}
那么我们在设置自己的序列化方式时,如上:
// 定制自己的序列化方式
simpleModule.addDeserializer(CheckChainDTO.class, new CheckChainDTOJsonDeserializer());
当然,Jackson提供了注解的方式,简单的实现,如上面直接在需要定制序列化的类上面添加的注解:
@JsonDeserialize(using = CheckChainDTOJsonDeserializer.class)
具体的反序列化过程类如下,枚举上直接定义了入参和出参的对应类型:
@Slf4j
public enum CheckTypeEnum {
/** SKU检查 */
SKU(CheckRequestSkuDTO.class, SkuResultDTO.class);
/** 请求入参类型 */
public final Class<? extends CheckRequestDTO> clazz;
/** 返回值类型 */
public final Class<? extends ResultDTO> resultClazz;
CheckTypeEnum(Class<? extends CheckRequestDTO> clazz,
Class<? extends ResultDTO> resultClazz) {
this.clazz = clazz;
this.resultClazz = resultClazz;
}
}
@Slf4j
public class CheckChainDTOJsonDeserializer extends JsonDeserializer<CheckChainDTO> {
@Override
public CheckChainDTO deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
try {
final String checkTypeEnumStr = node.get("checkTypeEnum").asText();
final CheckTypeEnum checkTypeEnum = CheckTypeEnum.valueOf(checkTypeEnumStr);
final String checkRequestDTOStr = node.get("checkRequestDTO").toString();
final CheckRequestDTO checkRequestDTO = JSONUtil.deserializeObject(checkRequestDTOStr, checkTypeEnum.clazz);
return new CheckChainDTO(checkTypeEnum, checkRequestDTO);
} catch (Exception e) {
log.error("检查接口调用链路异常:" + e);
buildException(PARAMETER_MATCH_EXCEPTION);
}
return null;
}
}
这样在项目使用的时候数据就不会丢失了,再根据具体的枚举类型,强转为对应的子类就可以了。