十三、微服务进阶之Core模块(序列化和反序列化)

十三、微服务进阶之Core模块(序列化和反序列化)

功能介绍

封装自己架构的核心功能模块,除此外还包含 feign、hystrix、redis、logger 这几个常用功能模块

数据转换

step1: 需求描述

前后端接口请求、feign接口请求、接口数据返回等都会涉及到数据转换;body 中的参数使用 Jackson 转换,请求路径中的参数先保存在 Map 然后根据接收类型转换;
针对一些特殊的数据类型默认的转换工具并不能正常处理,譬如时间类型的数据(java.util.Date、java.java.time.LocalDate、java.time.LocalDateTime、java.time.LocalTime);

step2: 场景重现

场景1:前端传递时间字符串(yyyy-MM-dd HH:mm:ss),接口使用 java 的时间类型对象接收,会报 “String can not convert to java.util.Date” 的错误;
场景2:接口返回了一个时间类型的数据,前端接收到的数据格式并非期望的(yyyy-MM-dd HH:mm:ss)格式,而是类似 “Wed Jun 02 15:50:13 CST 2000” 这样的数据;

step3: 解决方案

针对路径参数的转换

/** * LocalDate 转换器 */
@Bean
public Converter<String, LocalDate> localDateConverter() {
    return new Converter<String, LocalDate>() {
        @SneakyThrows
        @Override
        public LocalDate convert(@NonNull String source) {
            return LocalDate.parse(source, DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN));
        }
    };
}

/** * LocalDateTime 转换器 */
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
    return new Converter<String, LocalDateTime>() {
        @SneakyThrows
        @Override
        public LocalDateTime convert(@NonNull String source) {
            return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
        }
    };
}

/** * LocalTime 转换器 */
@Bean
public Converter<String, LocalTime> localTimeConverter() {
    return new Converter<String, LocalTime>() {
        @SneakyThrows
        @Override
        public LocalTime convert(@NonNull String source) {
            return LocalTime.parse(source, DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN));
        }
    };
}

/** * Date 转换器 */
@Bean
public Converter<String, Date> dateConverter() {
    return new Converter<String, Date>() {
        @SneakyThrows
        @Override
        public Date convert(@NonNull String source) {
            return DateUtil.parse(source);
        }
    };
}

针对 json 数据的转换

/** * 全局编码和解码设置,用于设置全局数据传输时的序列化设置 * 目前设置仅针对日期类做了特殊处理 * 其他类型采用 jackson 默认序列化和反序列化方式处理 * 定义成多例,保证其他组件可以继续定制私有化序列方式 */
@Bean
@Scope("prototype")
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    // 禁用默认的时间格式序列化方式 
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    // LocalDateTime 系列序列化和反序列化模块 
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
    javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
    javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
    // Date 序列化 
    javaTimeModule.addSerializer(Date.class, new JsonSerializer() {
        @Override
        public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeString(DateUtil.format(date, DatePattern.NORM_DATETIME_FORMAT));
        }
    });
    // Date 反序列化 
    javaTimeModule.addDeserializer(Date.class, new JsonDeserializer() {
        @SneakyThrows
        @Override
        public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
            return DateUtil.parse(jsonParser.getText());
        }
    });
    objectMapper.registerModule(javaTimeModule);
    return objectMapper;
}

注:此段代码即 rabbit 模块中序列化定制代码;json 转换工具依旧使用默认的 Jackson,但是需要重新定制化其中的转换规则,这个时候就需要重写 ObjectMapper,然后注册成 bean;

常用依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
        <!-- 不传递依赖,只是一个工具依赖 -->
    </dependency>
    <!-- spring 相关依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- 序列化工具, starter-web 项目自带 -->
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>
    <!-- 参数校验 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>
补充说明
  1. 为什么针对非 JSON 格式数据的转换只有反序列化配置,没有序列化的配置? 而 JSON 数据的转换则是序列化和反序列化都
    • 答:请求参数转换成 java 类型数据过程叫做 反序列化,java 类型的数据转换成字符串(json串)叫做 序列化;接口返回给前端的格式只有 JSON 一种(当前工程),所以针对 JSON 格式的数据需要序列化,而请求参数只需要反序列化成 java 对象即可;
  2. 为什么转换配置的 bean 并未生效?
    • 答:该现象出现在此配置模块作为依赖被其他模块引入时,此时这些 bean 可能会失效;原因是这些 bean 除了被注册成功后还需要被装载在特定的另一个 bean 中才会生效;如果转换类 bean 注册时间太晚会导致装载失败,所以需要指定转换 bean 的注册顺序;
    • 解决方案:将以上全部转换工具 bean 放在一个自动配置类中(@SpringBootConfiguration 或 @Configuration 修饰的类),指定当前自动配置类的顺序(@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE))即可;
  3. 为什么按照上问的解决方案后只有 json 数据的序列化和反序列化生效,路径参数反序列仍然失败?
    • 答:检查所有 Converter 的 bean,是否使用了 lambda 的方式简写;Converter 转换 bean 被装载时需要明确知道转换前后的两种格式类型,lambda 方式生成的匿名类不会生成对应的 class 文件,导致装载失败;
    • —— 可以参考 CSDN 上的一个帖子: Lambda替换匿名内部类引起的问题
    • —— 结论: Lambda 无法注入含不指明确定类型入参和出参方法的实现类