功能介绍
封装自己架构的核心功能模块,除此外还包含 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>
补充说明
- 为什么针对非 JSON 格式数据的转换只有反序列化配置,没有序列化的配置? 而 JSON 数据的转换则是序列化和反序列化都
- 答:请求参数转换成 java 类型数据过程叫做 反序列化,java 类型的数据转换成字符串(json串)叫做 序列化;接口返回给前端的格式只有 JSON 一种(当前工程),所以针对 JSON 格式的数据需要序列化,而请求参数只需要反序列化成 java 对象即可;
- 为什么转换配置的 bean 并未生效?
- 答:该现象出现在此配置模块作为依赖被其他模块引入时,此时这些 bean 可能会失效;原因是这些 bean 除了被注册成功后还需要被装载在特定的另一个 bean 中才会生效;如果转换类 bean 注册时间太晚会导致装载失败,所以需要指定转换 bean 的注册顺序;
- 解决方案:将以上全部转换工具 bean 放在一个自动配置类中(@SpringBootConfiguration 或 @Configuration 修饰的类),指定当前自动配置类的顺序(@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE))即可;
- 为什么按照上问的解决方案后只有 json 数据的序列化和反序列化生效,路径参数反序列仍然失败?
- 答:检查所有 Converter 的 bean,是否使用了 lambda 的方式简写;Converter 转换 bean 被装载时需要明确知道转换前后的两种格式类型,lambda 方式生成的匿名类不会生成对应的 class 文件,导致装载失败;
- —— 可以参考 CSDN 上的一个帖子: Lambda替换匿名内部类引起的问题
- —— 结论: Lambda 无法注入含不指明确定类型入参和出参方法的实现类