现在都是基于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;
    }
}

这样在项目使用的时候数据就不会丢失了,再根据具体的枚举类型,强转为对应的子类就可以了。