springboot项目配置序列化,反序列化器

向右看齐 2024-03-25 09:30 192阅读 0赞

介绍

本文介绍在项目中时间类型、枚举类型的序列化和反序列化自定义的处理类,也可以使用注解。

建议枚举都实现一个统一的接口,方便处理。我这定义了一个Dict接口。

枚举类型注解处理

这种方式比较灵活,可以让枚举按照自己的方式序列化,可以序列化code值(推荐),也可以序列化对象。序列化为对象:对于前端来说是比较好的,前端接收到code值和中文label,页面显示label就行了,但是对于后端,服务之间调用,使用feign调用的时候,序列化为对象,被调用服务的controller层就接收不到这个枚举值了,因为接收的是枚举序列化的对象,无法反序列化成枚举对象了。除非使用自定义反序列加判断去处理,比较麻烦。参考改进枚举工具类

定义统一枚举接口

  1. package com.common.interfaces;
  2. public interface Dict {
  3. String name();
  4. default Integer getCode() {
  5. return null;
  6. }
  7. default String getLabel() {
  8. return null;
  9. }
  10. }

序列化code值

枚举代码

  1. package com.common.enums.app;
  2. import com.baomidou.mybatisplus.annotation.EnumValue;
  3. import com.common.interfaces.Dict;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Getter;
  6. @Getter
  7. @AllArgsConstructor
  8. public enum DeliverDateModelEnum implements Dict {
  9. SAME(0, "相同时间"),
  10. DIFFERENT(2, "不同时间"),
  11. ;
  12. // mybatis的处理注解
  13. @EnumValue
  14. // Jackson的序列化处理注解;序列化code值
  15. @JsonValue
  16. private final Integer code;
  17. private final String label;
  18. }
  19. package com.common.enums.order;
  20. import com.common.exception.E;
  21. import com.common.interfaces.Dict;
  22. import com.fasterxml.jackson.annotation.JsonCreator;
  23. import lombok.AllArgsConstructor;
  24. import lombok.Getter;
  25. import java.util.Arrays;
  26. import java.util.Map;
  27. import java.util.stream.Collectors;
  28. /**
  29. * 订单采购方式枚举
  30. */
  31. @Getter
  32. @AllArgsConstructor
  33. public enum OrderPurchaseMethodEnum implements Dict {
  34. INQUIRY("询价"),
  35. MALL("商城"),
  36. ;
  37. private String label;
  38. private static final Map<String, OrderPurchaseMethodEnum> MAP =
  39. Arrays.stream(OrderPurchaseMethodEnum.values()).collect(Collectors.toMap(Enum::name, e -> e));
  40. @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
  41. public static OrderPurchaseMethodEnum resolve(String code) {
  42. if (!MAP.containsKey(code)) {
  43. throw new Exception("枚举类型错误");
  44. }
  45. return MAP.get(code);
  46. }
  47. }

实体类

  1. package com.app.dto;
  2. import com.common.enums.apply.DeliverDateModelEnum;
  3. import com.common.enums.order.OrderPurchaseMethodEnum;
  4. import io.swagger.annotations.ApiModelProperty;
  5. import lombok.Data;
  6. import lombok.EqualsAndHashCode;
  7. import javax.validation.constraints.NotBlank;
  8. import javax.validation.constraints.NotNull;
  9. import java.io.Serializable;
  10. @Data
  11. @EqualsAndHashCode(callSuper = false)
  12. public class ApplyInfoDTO2 implements Serializable {
  13. private static final long serialVersionUID = 1L;
  14. @ApiModelProperty(value = "主键")
  15. private Long id;
  16. @ApiModelProperty(value = "标题", required = true)
  17. private String title;
  18. @NotNull(message = "时间方式不能为空")
  19. private DeliverDateModelEnum deliverDateModel;
  20. // 后期会用到
  21. private OrderPurchaseMethodEnum purchaseMethod;
  22. }

测试代码

  1. @RestController
  2. @Slf4j
  3. @RequestMapping("/api/test")
  4. public class TestController2 {
  5. @PostMapping(value = "/hh")
  6. public ApplyInfoDTO2 test9(@RequestBody ApplyInfoDTO2 applyInfoDTO) {
  7. System.out.println("hhhhh");
  8. applyInfoDTO.setId(55L);
  9. System.out.println("----"+ JacksonUtils.toJson(applyInfoDTO));
  10. return applyInfoDTO;
  11. }
  12. }
  13. 请求参数
  14. {
  15. "id":11,
  16. "title": "dajf",
  17. "deliverDateModel":2
  18. }
  19. 响应结果
  20. {
  21. "id": 55,
  22. "title": "dajf",
  23. "deliverDateModel": 2,
  24. "purchaseMethod": null
  25. }

序列化对象

这里只改枚举就可以了

  1. package com.common.enums.app;
  2. import com.baomidou.mybatisplus.annotation.EnumValue;
  3. import com.common.interfaces.Dict;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Getter;
  6. @Getter
  7. @AllArgsConstructor
  8. @JsonFormat(shape = JsonFormat.Shape.OBJECT)
  9. public enum DeliverDateModelEnum implements Dict {
  10. SAME(0, "相同时间"),
  11. DIFFERENT(2, "不同时间"),
  12. ;
  13. // mybatis的处理注解
  14. @EnumValue
  15. // 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
  16. // @JsonValue
  17. private final Integer code;
  18. private final String label;
  19. }

结果

  1. 请求参数
  2. {
  3. "id":11,
  4. "title": "dajf",
  5. "deliverDateModel":0
  6. }
  7. 响应结果
  8. {
  9. "id": 55,
  10. "title": "dajf",
  11. "deliverDateModel": {
  12. "code": 0,
  13. "label": "相同时间"
  14. },
  15. "purchaseMethod": null
  16. }

反序列化处理

这就是说,前端传code值0,后端可以对应到枚举字段上,默认的是使用下标ordinal来反序列化的,按照0,1,2,3…去对应上,如果中跳过了,接收值的时候就会报错。比如上边的枚举类DeliverDateModelEnum,传0不会报错,传2就找不到了,错误如下,

  1. Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.common.enums.apply.DeliverDateModelEnum` from number 2: index value outside legal index range [0..1]
  2. at [Source: (PushbackInputStream); line: 4, column: 21] (through reference chain: com.cnpc.app.dto.ApplyInfoDTO2["deliverDateModel"])

解决方案:在枚举类加反序列化处理代码@JsonCreator

  1. package com.common.enums.app;
  2. import com.baomidou.mybatisplus.annotation.EnumValue;
  3. import com.common.interfaces.Dict;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Getter;
  6. @Getter
  7. @AllArgsConstructor
  8. @JsonFormat(shape = JsonFormat.Shape.OBJECT)
  9. public enum DeliverDateModelEnum implements Dict {
  10. SAME(0, "相同时间"),
  11. DIFFERENT(2, "不同时间"),
  12. ;
  13. // mybatis的处理注解
  14. @EnumValue
  15. // 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
  16. // @JsonValue
  17. private final Integer code;
  18. private final String label;
  19. private static final Map<Integer, DeliverDateModelEnum> map =
  20. Arrays.stream(DeliverDateModelEnum.values()).collect(Collectors.toMap(DeliverDateModelEnum::getCode, e -> e));
  21. @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
  22. public static DeliverDateModelEnum resolve(Integer code) {
  23. if (!map.containsKey(code)) {
  24. throw new E("找不到枚举");
  25. }
  26. return map.get(code);
  27. }
  28. }

以上就是注解的枚举处理方法,统一定义接口后,为了前端可以通过code值展示枚举中午label,所以又写了一个字典解析类,将所以实现Dict接口的枚举,都拿到,然后解析存储,再给前端提供一个请求路径。前端存储起来,去解析,退出的时候,可以清除前端缓存。

将枚举转字典存储

  1. package com.dict.service;
  2. import com.common.annotation.DictType;
  3. import com.common.exception.E;
  4. import com.common.interfaces.Dict;
  5. import com.dict.vo.DictItemVO;
  6. import com.dict.vo.DictVO;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.apache.logging.log4j.util.Strings;
  10. import org.reflections.Reflections;
  11. import org.springframework.beans.factory.annotation.Value;
  12. import org.springframework.stereotype.Component;
  13. import javax.annotation.PostConstruct;
  14. import java.lang.reflect.InvocationTargetException;
  15. import java.lang.reflect.Method;
  16. import java.util.*;
  17. import java.util.stream.Collectors;
  18. /**
  19. * 系统字典Service
  20. */
  21. @Slf4j
  22. @Component
  23. public class SystemDictService {
  24. /**
  25. * 反射要扫描的包路径
  26. */
  27. @Value("${dict.scanPath:com.company}")
  28. private String scanPath;
  29. /**
  30. * 字典集合
  31. */
  32. private static Set<DictVO> DICT_SET = new HashSet<>();
  33. /**
  34. * 提供给外部获取字典集合的方法
  35. *
  36. * @author sun
  37. */
  38. public Set<DictVO> getDictSet() {
  39. return DICT_SET;
  40. }
  41. /**
  42. * 启动时扫描
  43. */
  44. @PostConstruct
  45. public void initDict() {
  46. DICT_SET = scanDict();
  47. }
  48. /**
  49. * 扫描字典列表
  50. */
  51. private Set<DictVO> scanDict() {
  52. // 反射要扫描的包路径
  53. Reflections reflections = new Reflections(scanPath);
  54. // 反射获取所有实现 DictInterface 接口的枚举
  55. Set<Class<? extends Dict>> monitorClasses = reflections.getSubTypesOf(Dict.class);
  56. /*
  57. * 封装字典列表
  58. * 过滤掉不是枚举的实现
  59. * 反射调用枚举的 values 方法, 获取每个枚举内部的值列表
  60. */
  61. return monitorClasses.stream()
  62. .filter(Class::isEnum)
  63. .map(sub -> {
  64. DictVO vo = new DictVO();
  65. try {
  66. /* 这块没有使用到
  67. DictType annotation = sub.getAnnotation(DictType.class);
  68. if (Objects.nonNull(annotation) && Strings.isNotBlank(annotation.type())) {
  69. // 有DictType注解并且type不是空白时使用注解中的值作为字典的类别
  70. vo.setType(annotation.type());
  71. } else {
  72. // 否则使用类名作为字典的类别
  73. vo.setType(sub.getSimpleName());
  74. }*/
  75. Method valuesMethod = sub.getMethod("values");
  76. Object valuesObj = valuesMethod.invoke(sub);
  77. Dict[] values = (Dict[]) valuesObj;
  78. List<DictItemVO> collect = Arrays.stream(values)
  79. .map(item -> {
  80. // code和label都可以没有,全部以name为默认
  81. String code = item.getCode() != null ? item.getCode().toString() : item.name();
  82. String label = item.getLabel() != null ? item.getLabel() : item.name();
  83. return new DictItemVO(code, label);
  84. }).collect(Collectors.toList());
  85. vo.setItems(collect);
  86. } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
  87. log.error("{}", e.getMessage(), e);
  88. }
  89. return vo;
  90. })
  91. // 这里转map是校验有没有重复的type, 如果存在重复的会报错
  92. .collect(Collectors.toMap(item -> item, item -> item, (a1, a2) -> {throw new E("字典类型:" + a1.getType() + ", 有重复");}))
  93. .keySet();
  94. }
  95. }
  96. // controller层提供外部访问
  97. @ApiOperation("系统字典列表查询")
  98. @GetMapping(value = "/system")
  99. public R<Set<DictVO>> querySystemDict() {
  100. return R.of(systemDictService.getDictSet());
  101. }
  102. package com.dict.vo;
  103. import lombok.Data;
  104. import java.util.List;
  105. import java.util.Objects;
  106. /**
  107. * 字典VO
  108. *
  109. */
  110. @Data
  111. public class DictVO {
  112. /**
  113. * 字典类别名称
  114. */
  115. private String type;
  116. /**
  117. * 字典项列表
  118. */
  119. private List<DictItemVO> items;
  120. @Override
  121. public boolean equals(Object o) {
  122. if (this == o) {
  123. return true;
  124. }
  125. if (o == null || getClass() != o.getClass()) {
  126. return false;
  127. }
  128. DictVO dictVO = (DictVO) o;
  129. return Objects.equals(type, dictVO.type);
  130. }
  131. @Override
  132. public int hashCode() {
  133. return Objects.hash(type);
  134. }
  135. }
  136. package com.dict.vo;
  137. import lombok.AllArgsConstructor;
  138. import lombok.Data;
  139. import lombok.NoArgsConstructor;
  140. /**
  141. * 字典项
  142. */
  143. @Data
  144. @NoArgsConstructor
  145. @AllArgsConstructor
  146. public class DictItemVO {
  147. /**
  148. * 编码
  149. */
  150. private String code;
  151. /**
  152. * 标签
  153. */
  154. private String label;
  155. }

自定义枚举类型处理(不建议使用)

这种方式只能支持单个属性接收值,接收list枚举,map。set接收枚举都会报错。

由于上边规定所有的枚举都需要实现Dict接口,下面的反序列化只针对符合条件的处理

处理工具类

  1. package com.common.config;
  2. import com.common.interfaces.Dict;
  3. import java.util.Arrays;
  4. import java.util.Map;
  5. import java.util.Objects;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. import java.util.stream.Collectors;
  8. /**
  9. * 枚举匹配工具类
  10. */
  11. public class EnumUtil {
  12. private static final Map<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP =
  13. new ConcurrentHashMap<>(16);
  14. @SuppressWarnings("unchecked")
  15. public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) {
  16. Map enumMap = CLASS_ENUM_MAP.get(enumClass);
  17. if (Objects.isNull(enumMap)) {
  18. // Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
  19. // 这种表达式写法会报错
  20. // .collect(Collectors.toMap(Dict::getCode, v -> v));
  21. Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
  22. .collect(Collectors.toMap(obj -> obj.getCode(), v -> v));
  23. CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
  24. return (E) unmodifiableMap.get(type);
  25. }
  26. return (E) enumMap.get(type);
  27. }
  28. }

自定义的反序列化类

  1. package com.common.config;
  2. import com.common.interfaces.Dict;
  3. import com.fasterxml.jackson.core.JsonParser;
  4. import com.fasterxml.jackson.databind.BeanProperty;
  5. import com.fasterxml.jackson.databind.DeserializationContext;
  6. import com.fasterxml.jackson.databind.JavaType;
  7. import com.fasterxml.jackson.databind.JsonDeserializer;
  8. import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
  9. import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  10. import java.io.IOException;
  11. /**
  12. * 枚举反序列化器
  13. */
  14. public class DictEnumDeserializer extends StdDeserializer<Dict> implements ContextualDeserializer {
  15. public DictEnumDeserializer() {
  16. super((JavaType) null);
  17. }
  18. public DictEnumDeserializer(JavaType valueType) {
  19. super(valueType);
  20. }
  21. @Override
  22. public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
  23. return new DictEnumDeserializer(property.getType());
  24. }
  25. @Override
  26. @SuppressWarnings("all")
  27. public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
  28. return (Dict) EnumUtil.match((Class) _valueClass, p.getIntValue());
  29. }
  30. }

注入到spring中

注入方式1

由于项目中还配置有时间的序列化所以就把它们放一起了。建议使用的,把所有的序列化反序列化的都放这。

  1. package com.cnpc.common.config;
  2. import com.common.interfaces.Dict;
  3. import com.fasterxml.jackson.databind.*;
  4. import com.fasterxml.jackson.databind.module.SimpleDeserializers;
  5. import com.fasterxml.jackson.databind.module.SimpleModule;
  6. import com.fasterxml.jackson.databind.type.ClassKey;
  7. import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
  8. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
  9. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
  10. import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
  11. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
  12. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
  13. import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
  14. import org.springframework.context.annotation.Bean;
  15. import org.springframework.context.annotation.Configuration;
  16. import org.springframework.context.annotation.Primary;
  17. import java.time.LocalDate;
  18. import java.time.LocalDateTime;
  19. import java.time.LocalTime;
  20. import java.time.format.DateTimeFormatter;
  21. import java.util.Objects;
  22. /**
  23. * LocalDateTime配置
  24. */
  25. @Configuration
  26. // public class LocalDateTimeFormatConfig { 改了一个名字
  27. public class WebCustomerConfig {
  28. private static final String DEFAULT_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
  29. private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
  30. private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";
  31. // 这里加@Primary,在项目启动的时候一共会加载12个convert,第7个是MappingJackson2HttpMessageConverter
  32. // 处理json映射对象参数的类,会采用这个ObjectMapper,默认的是 new ObjectMapper();这里自己增加了一些序列化处理的方法
  33. @Bean
  34. @Primary
  35. public ObjectMapper objectMapper() {
  36. ObjectMapper objectMapper = new ObjectMapper();
  37. // 注册时间的序列化和反序列化处理
  38. JavaTimeModule javaTimeModule = new JavaTimeModule();
  39. javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN)));
  40. javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));
  41. javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));
  42. javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN)));
  43. javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));
  44. javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));
  45. objectMapper.registerModule(javaTimeModule);
  46. //忽略识别不了的属性
  47. objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  48. // 注册枚举的处理序列化和反序列的方式
  49. SimpleModule sm = new SimpleModule();
  50. //自定义查找规则
  51. sm.setDeserializers(new SimpleDeserializers() {
  52. @Override
  53. public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config,
  54. BeanDescription beanDesc) throws JsonMappingException {
  55. JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);
  56. if (enumDeserializer != null) {
  57. return enumDeserializer;
  58. }
  59. //遍历枚举实现的接口, 查找反序列化器
  60. for (Class typeInterface : type.getInterfaces()) {
  61. // 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现Dict接口,可以去掉下面这个代码
  62. if (typeInterface.equals(Dict.class) ){
  63. Dict[] enumConstants = (Dict[]) type.getEnumConstants();
  64. if (Objects.isNull(enumConstants[0].getCode())) {
  65. return null;
  66. }
  67. }
  68. enumDeserializer = this._classMappings.get(new ClassKey(typeInterface));
  69. if (enumDeserializer != null) {
  70. return enumDeserializer;
  71. }
  72. }
  73. return null;
  74. }
  75. });
  76. sm.addDeserializer(Dict.class, new TypeEnumDeserializer());
  77. // 增加枚举的序列化方式
  78. sm.addSerializer(Dict.class, new JsonSerializer<Dict>() {
  79. @Override
  80. public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  81. // 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
  82. // gen.writeStartObject();
  83. // gen.writeNumberField("code", value.getCode());
  84. // gen.writeStringField("label", value.getLabel());
  85. // gen.writeEndObject();
  86. // 相当于在枚举code字段上加 @JsonValue 返回给页面一个code值
  87. gen.writeNumber(value.getCode());
  88. }
  89. @Override
  90. public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers,
  91. TypeSerializer typeSer) throws IOException {
  92. WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
  93. serialize(value, gen, serializers);
  94. typeSer.writeTypeSuffix(gen, typeIdDef);
  95. }
  96. });
  97. objectMapper.registerModule(sm);
  98. return objectMapper;
  99. }
  100. }

注入方式2

下面这种注入会影响项目已经有配置ObjectMapper的地方,建议都放一起。如果采用下面这种方式,需要自己创建一个MappingJackson2HttpMessageConverter,并将converter添加到list的第一个,如果添加到最后一个,他会先找list中前边的MappingJackson2HttpMessageConverter,大约是第7个位置,就不会用自己写的。

  1. package com.common.config;
  2. import com.common.interfaces.Dict;
  3. import com.fasterxml.jackson.core.JsonGenerator;
  4. import com.fasterxml.jackson.core.JsonToken;
  5. import com.fasterxml.jackson.core.type.WritableTypeId;
  6. import com.fasterxml.jackson.databind.*;
  7. import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
  8. import com.fasterxml.jackson.databind.module.SimpleDeserializers;
  9. import com.fasterxml.jackson.databind.module.SimpleModule;
  10. import com.fasterxml.jackson.databind.type.ClassKey;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.springframework.http.converter.HttpMessageConverter;
  13. import org.springframework.http.converter.StringHttpMessageConverter;
  14. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  15. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  16. import java.io.IOException;
  17. import java.nio.charset.StandardCharsets;
  18. import java.util.List;
  19. /**
  20. * 装载枚举序列化器
  21. */
  22. @Configuration
  23. @Slf4j
  24. public class WebMvcConfig implements WebMvcConfigurer {
  25. @Override
  26. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  27. StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
  28. MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  29. converter.setObjectMapper(objectMapperForWebConvert());
  30. converters.add(0, stringHttpMessageConverter);
  31. // 将这个MappingJackson2HttpMessageConverter添加到第一个,是为了优先找到,否则就有其他MappingJackson2HttpMessageConverter去处理了
  32. converters.add(0, converter);
  33. }
  34. // 这个没有时间的处理配置。
  35. public ObjectMapper objectMapperForWebConvert() {
  36. ObjectMapper om = new ObjectMapper();
  37. om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  38. SimpleModule sm = new SimpleModule();
  39. //自定义查找规则
  40. sm.setDeserializers(new SimpleDeserializers() {
  41. @Override
  42. public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config,
  43. BeanDescription beanDesc) throws JsonMappingException {
  44. JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);
  45. if (enumDeserializer != null) {
  46. return enumDeserializer;
  47. }
  48. //遍历枚举实现的接口, 查找反序列化器
  49. for (Class typeInterface : type.getInterfaces()) {
  50. // 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现Dict接口,可以去掉下面这个代码
  51. if (typeInterface.equals(Dict.class)){
  52. Dict[] enumConstants = (Dict[]) type.getEnumConstants();
  53. if (Objects.isNull(enumConstants[0].getCode())) {
  54. return null;
  55. }
  56. }
  57. // 这里的classKey不要导错包,必须是com.fasterxml.jackson.databind.type.ClassKey,否则会找不到,导致自定义的反序列化类不起作用
  58. enumDeserializer = this._classMappings.get(new ClassKey(typeInterface));
  59. if (enumDeserializer != null) {
  60. return enumDeserializer;
  61. }
  62. // 上边的ClassKey导入包错误,找不到临时加的
  63. // if (typeInterface.equals(Dict.class)){
  64. // return new DictEnumDeserializer();
  65. // }
  66. // return new DictEnumDeserializer();
  67. }
  68. return null;
  69. }
  70. });
  71. // 添加枚举反序列化处理器
  72. sm.addDeserializer(Dict.class, new DictEnumDeserializer());
  73. sm.addSerializer(Dict.class, new JsonSerializer<Dict>() {
  74. @Override
  75. public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  76. // 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
  77. // gen.writeStartObject();
  78. // gen.writeNumberField("code", value.getCode());
  79. // gen.writeStringField("label", value.getLabel());
  80. // gen.writeEndObject();
  81. // 相当于在枚举code字段上加 @JsonValue 返回给页面一个code值
  82. gen.writeNumber(value.getCode());
  83. }
  84. @Override
  85. public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers,
  86. TypeSerializer typeSer) throws IOException {
  87. WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
  88. serialize(value, gen, serializers);
  89. typeSer.writeTypeSuffix(gen, typeIdDef);
  90. }
  91. });
  92. om.registerModule(sm);
  93. return om;
  94. }
  95. }

这样就可以测试了。没有实现Dict接口的枚举会采用下一个MappingJackson2HttpMessageConverter的new ObjectMapper()去处理,按照默认的去处理

断点测试

715affc2d1274dc19296c8f6f1644d22.png

第一次赋值

6d17819ca1a57fe01fb31fe5b4c0f5d0.png

加载自定义的ObjectMapper

ff94ffd0a6cda4207e4fd7f5e8dedf37.png

现在converter变成了14个了,因为这是注入方式2,自己往里面放了2个,

262c12ffe3c9b82e656bcdbb362ab1de.png

请求访问时,断点

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)

c7c567fde1e8d4d93a409db6793be2bb.png

注入方式1断点测试

168672c74bae4cc74fee0f522a30c125.png

启动过程中会放入2个MappingJackson,会看到第7个下移到第8个了

627f25fb2c71f1c8ea415ca14fc25d37.png

注入方式2断点测试

第一次启动地址是15536

eb8bdf484a7c734a2991bee7b7ae0880.png

6f05eeef35ec475b8e99ee3d411322d1.png

a82cd6d87b0164cc195341b6e6a7b50c.png

走到自己写的枚举处理

62ca0dd27e68eafe89a0c942f7547a8e.png

改进枚举工具类

改进之后,即可已返回给前端枚举对象,也可以接收对象类型的,也可以接收数值,也可以接收字符串

  1. package com.common.config;
  2. import com.common.interfaces.Dict;
  3. import com.fasterxml.jackson.core.JsonParser;
  4. import com.fasterxml.jackson.databind.BeanProperty;
  5. import com.fasterxml.jackson.databind.DeserializationContext;
  6. import com.fasterxml.jackson.databind.JavaType;
  7. import com.fasterxml.jackson.databind.JsonDeserializer;
  8. import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
  9. import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  10. import java.io.IOException;
  11. /**
  12. * 枚举反序列化器
  13. */
  14. public class DictEnumDeserializer extends StdDeserializer<Dict> implements ContextualDeserializer {
  15. public DictEnumDeserializer() {
  16. super((JavaType) null);
  17. }
  18. public DictEnumDeserializer(JavaType valueType) {
  19. super(valueType);
  20. }
  21. @Override
  22. public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
  23. return new DictEnumDeserializer(property.getType());
  24. }
  25. // 自定义的反序列化器这个方法,需要传JsonParser
  26. @Override
  27. @SuppressWarnings("all")
  28. public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
  29. return (Dict) EnumUtil.match((Class) _valueClass, p);
  30. }
  31. }
  32. package com.common.config;
  33. import com.common.interfaces.Dict;
  34. import com.fasterxml.jackson.core.JsonParser;
  35. import com.fasterxml.jackson.core.JsonToken;
  36. import java.io.IOException;
  37. import java.util.Arrays;
  38. import java.util.Map;
  39. import java.util.Objects;
  40. import java.util.concurrent.ConcurrentHashMap;
  41. import java.util.stream.Collectors;
  42. /**
  43. * 枚举匹配工具类
  44. * 改进的枚举工具
  45. */
  46. public class EnumUtil {
  47. private static final Map<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP =
  48. new ConcurrentHashMap<>(16);
  49. @SuppressWarnings("unchecked")
  50. public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, JsonParser jsonParser) throws IOException {
  51. Integer code = null;
  52. // 接收数值型的
  53. if (jsonParser.currentToken() == JsonToken.VALUE_NUMBER_INT){
  54. code = jsonParser.getValueAsInt();
  55. // 接收OBJECT类型的参数
  56. } else if (jsonParser.currentToken() == JsonToken.START_OBJECT){
  57. while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
  58. String fieldname = jsonParser.getCurrentName();
  59. if ("code".equals(fieldname)) {
  60. jsonParser.nextToken();
  61. code = jsonParser.getValueAsInt();
  62. break;
  63. }
  64. }
  65. // 接收字符串类型的,需要判断是纯数字
  66. }else if (jsonParser.currentToken() == JsonToken.VALUE_STRING){
  67. String codestr = jsonParser.getValueAsString();
  68. if (codestr.matches("^[0-9]*$")) {
  69. code = Integer.valueOf(codestr);
  70. }
  71. }
  72. if (Objects.isNull(code)){
  73. throw new RuntimeException("没有code找不到对应的枚举");
  74. }
  75. return getDictEnum(enumClass, code);
  76. }
  77. private static <E extends Enum<E> & Dict> E getDictEnum(Class<E> enumClass, Integer type) {
  78. Map enumMap = CLASS_ENUM_MAP.get(enumClass);
  79. if (Objects.isNull(enumMap)) {
  80. Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
  81. .collect(Collectors.toMap(obj -> obj.getCode(), v -> v));
  82. CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
  83. E e = (E) unmodifiableMap.get(code);
  84. if (Objects.isNull(e)){
  85. throw new RuntimeException("指定类型code找不到对应的枚举code = " + code);
  86. }
  87. return e;
  88. }
  89. E e = (E) enumMap.get(code);
  90. if (Objects.isNull(e)){
  91. throw new RuntimeException("指定类型code找不到对应的枚举code = " + code);
  92. }
  93. return e;
  94. }
  95. // 原来的方法,只能接收数值code
  96. /*public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) {
  97. Map enumMap = CLASS_ENUM_MAP.get(enumClass);
  98. if (Objects.isNull(enumMap)) {
  99. Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
  100. .collect(Collectors.toMap(Dict::getCode, v -> v));
  101. CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
  102. return (E) unmodifiableMap.get(type);
  103. }
  104. return (E) enumMap.get(type);
  105. }*/
  106. }

参考文章

https://developer.aliyun.com/article/979501

http://events.jianshu.io/p/33e537ea6f10

JsonParser的处理

https://blog.csdn.net/band_mmbx/article/details/126749515

发表评论

表情:
评论列表 (有 0 条评论,192人围观)

还没有评论,来说两句吧...

相关阅读

    相关 序列序列

    序列化: 对象的序列化主要有两种用途:   1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;   2) 在网络上传送对象的字节序列。