springboot序列化和反序列化器配置方法
介紹
本文介紹在項(xiàng)目中時間類型、枚舉類型的序列化和反序列化自定義的處理類,也可以使用注解。
建議枚舉都實(shí)現(xiàn)一個統(tǒng)一的接口,方便處理。我這定義了一個Dict接口。
枚舉類型注解處理
這種方式比較靈活,可以讓枚舉按照自己的方式序列化,可以序列化code值(推薦),也可以序列化對象。序列化為對象:對于前端來說是比較好的,前端接收到code值和中文label,頁面顯示label就行了,但是對于后端,服務(wù)之間調(diào)用,使用feign調(diào)用的時候,序列化為對象,被調(diào)用服務(wù)的controller層就接收不到這個枚舉值了,因?yàn)榻邮盏氖敲杜e序列化的對象,無法反序列化成枚舉對象了。除非使用自定義反序列加判斷去處理,比較麻煩。參考改進(jìn)枚舉工具類
定義統(tǒng)一枚舉接口
package com.common.interfaces; public interface Dict { String name(); default Integer getCode() { return null; } default String getLabel() { return null; } }
序列化code值
枚舉代碼
package com.common.enums.app; import com.baomidou.mybatisplus.annotation.EnumValue; import com.common.interfaces.Dict; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum DeliverDateModelEnum implements Dict { SAME(0, "相同時間"), DIFFERENT(2, "不同時間"), ; // mybatis的處理注解 @EnumValue // Jackson的序列化處理注解;序列化code值 @JsonValue private final Integer code; private final String label; }
package com.common.enums.order; import com.common.exception.E; import com.common.interfaces.Dict; import com.fasterxml.jackson.annotation.JsonCreator; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; /** * 訂單采購方式枚舉 */ @Getter @AllArgsConstructor public enum OrderPurchaseMethodEnum implements Dict { INQUIRY("詢價"), MALL("商城"), ; private String label; private static final Map<String, OrderPurchaseMethodEnum> MAP = Arrays.stream(OrderPurchaseMethodEnum.values()).collect(Collectors.toMap(Enum::name, e -> e)); @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static OrderPurchaseMethodEnum resolve(String code) { if (!MAP.containsKey(code)) { throw new Exception("枚舉類型錯誤"); } return MAP.get(code); } }
實(shí)體類
package com.app.dto; import com.common.enums.apply.DeliverDateModelEnum; import com.common.enums.order.OrderPurchaseMethodEnum; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.io.Serializable; @Data @EqualsAndHashCode(callSuper = false) public class ApplyInfoDTO2 implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主鍵") private Long id; @ApiModelProperty(value = "標(biāo)題", required = true) private String title; @NotNull(message = "時間方式不能為空") private DeliverDateModelEnum deliverDateModel; // 后期會用到 private OrderPurchaseMethodEnum purchaseMethod; }
測試代碼
@RestController @Slf4j @RequestMapping("/api/test") public class TestController2 { @PostMapping(value = "/hh") public ApplyInfoDTO2 test9(@RequestBody ApplyInfoDTO2 applyInfoDTO) { System.out.println("hhhhh"); applyInfoDTO.setId(55L); System.out.println("----"+ JacksonUtils.toJson(applyInfoDTO)); return applyInfoDTO; } }
請求參數(shù) { "id":11, "title": "dajf", "deliverDateModel":2 } 響應(yīng)結(jié)果 { "id": 55, "title": "dajf", "deliverDateModel": 2, "purchaseMethod": null }
序列化對象
這里只改枚舉就可以了
package com.common.enums.app; import com.baomidou.mybatisplus.annotation.EnumValue; import com.common.interfaces.Dict; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum DeliverDateModelEnum implements Dict { SAME(0, "相同時間"), DIFFERENT(2, "不同時間"), ; // mybatis的處理注解 @EnumValue // 由于上邊注解標(biāo)記序列化為對象了,這就不起作用了,可以注掉 // @JsonValue private final Integer code; private final String label; }
結(jié)果
請求參數(shù)
{
"id":11,
"title": "dajf",
"deliverDateModel":0
}
響應(yīng)結(jié)果
{
"id": 55,
"title": "dajf",
"deliverDateModel": {
"code": 0,
"label": "相同時間"
},
"purchaseMethod": null
}
反序列化處理
這就是說,前端傳code值0,后端可以對應(yīng)到枚舉字段上,默認(rèn)的是使用下標(biāo)ordinal來反序列化的,按照0,1,2,3...去對應(yīng)上,如果中跳過了,接收值的時候就會報(bào)錯。比如上邊的枚舉類DeliverDateModelEnum,傳0不會報(bào)錯,傳2就找不到了,錯誤如下,
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]
at [Source: (PushbackInputStream); line: 4, column: 21] (through reference chain: com.cnpc.app.dto.ApplyInfoDTO2["deliverDateModel"])
解決方案:在枚舉類加反序列化處理代碼@JsonCreator
package com.common.enums.app; import com.baomidou.mybatisplus.annotation.EnumValue; import com.common.interfaces.Dict; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum DeliverDateModelEnum implements Dict { SAME(0, "相同時間"), DIFFERENT(2, "不同時間"), ; // mybatis的處理注解 @EnumValue // 由于上邊注解標(biāo)記序列化為對象了,這就不起作用了,可以注掉 // @JsonValue private final Integer code; private final String label; private static final Map<Integer, DeliverDateModelEnum> map = Arrays.stream(DeliverDateModelEnum.values()).collect(Collectors.toMap(DeliverDateModelEnum::getCode, e -> e)); @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static DeliverDateModelEnum resolve(Integer code) { if (!map.containsKey(code)) { throw new E("找不到枚舉"); } return map.get(code); } }
以上就是注解的枚舉處理方法,統(tǒng)一定義接口后,為了前端可以通過code值展示枚舉中午label,所以又寫了一個字典解析類,將所以實(shí)現(xiàn)Dict接口的枚舉,都拿到,然后解析存儲,再給前端提供一個請求路徑。前端存儲起來,去解析,退出的時候,可以清除前端緩存。
將枚舉轉(zhuǎn)字典存儲
package com.dict.service; import com.common.annotation.DictType; import com.common.exception.E; import com.common.interfaces.Dict; import com.dict.vo.DictItemVO; import com.dict.vo.DictVO; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.reflections.Reflections; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.stream.Collectors; /** * 系統(tǒng)字典Service */ @Slf4j @Component public class SystemDictService { /** * 反射要掃描的包路徑 */ @Value("${dict.scanPath:com.company}") private String scanPath; /** * 字典集合 */ private static Set<DictVO> DICT_SET = new HashSet<>(); /** * 提供給外部獲取字典集合的方法 * * @author sun */ public Set<DictVO> getDictSet() { return DICT_SET; } /** * 啟動時掃描 */ @PostConstruct public void initDict() { DICT_SET = scanDict(); } /** * 掃描字典列表 */ private Set<DictVO> scanDict() { // 反射要掃描的包路徑 Reflections reflections = new Reflections(scanPath); // 反射獲取所有實(shí)現(xiàn) DictInterface 接口的枚舉 Set<Class<? extends Dict>> monitorClasses = reflections.getSubTypesOf(Dict.class); /* * 封裝字典列表 * 過濾掉不是枚舉的實(shí)現(xiàn) * 反射調(diào)用枚舉的 values 方法, 獲取每個枚舉內(nèi)部的值列表 */ return monitorClasses.stream() .filter(Class::isEnum) .map(sub -> { DictVO vo = new DictVO(); try { /* 這塊沒有使用到 DictType annotation = sub.getAnnotation(DictType.class); if (Objects.nonNull(annotation) && Strings.isNotBlank(annotation.type())) { // 有DictType注解并且type不是空白時使用注解中的值作為字典的類別 vo.setType(annotation.type()); } else { // 否則使用類名作為字典的類別 vo.setType(sub.getSimpleName()); }*/ Method valuesMethod = sub.getMethod("values"); Object valuesObj = valuesMethod.invoke(sub); Dict[] values = (Dict[]) valuesObj; List<DictItemVO> collect = Arrays.stream(values) .map(item -> { // code和label都可以沒有,全部以name為默認(rèn) String code = item.getCode() != null ? item.getCode().toString() : item.name(); String label = item.getLabel() != null ? item.getLabel() : item.name(); return new DictItemVO(code, label); }).collect(Collectors.toList()); vo.setItems(collect); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { log.error("{}", e.getMessage(), e); } return vo; }) // 這里轉(zhuǎn)map是校驗(yàn)有沒有重復(fù)的type, 如果存在重復(fù)的會報(bào)錯 .collect(Collectors.toMap(item -> item, item -> item, (a1, a2) -> {throw new E("字典類型:" + a1.getType() + ", 有重復(fù)");})) .keySet(); } }
// controller層提供外部訪問 @ApiOperation("系統(tǒng)字典列表查詢") @GetMapping(value = "/system") public R<Set<DictVO>> querySystemDict() { return R.of(systemDictService.getDictSet()); }
package com.dict.vo; import lombok.Data; import java.util.List; import java.util.Objects; /** * 字典VO * */ @Data public class DictVO { /** * 字典類別名稱 */ private String type; /** * 字典項(xiàng)列表 */ private List<DictItemVO> items; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DictVO dictVO = (DictVO) o; return Objects.equals(type, dictVO.type); } @Override public int hashCode() { return Objects.hash(type); } }
package com.dict.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 字典項(xiàng) */ @Data @NoArgsConstructor @AllArgsConstructor public class DictItemVO { /** * 編碼 */ private String code; /** * 標(biāo)簽 */ private String label; }
自定義枚舉類型處理(不建議使用)
這種方式只能支持單個屬性接收值,接收list枚舉,map。set接收枚舉都會報(bào)錯。
由于上邊規(guī)定所有的枚舉都需要實(shí)現(xiàn)Dict接口,下面的反序列化只針對符合條件的處理
處理工具類
package com.common.config; import com.common.interfaces.Dict; import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * 枚舉匹配工具類 */ public class EnumUtil { private static final Map<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP = new ConcurrentHashMap<>(16); @SuppressWarnings("unchecked") public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { // Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants()) // 這種表達(dá)式寫法會報(bào)錯 // .collect(Collectors.toMap(Dict::getCode, v -> v)); Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants()) .collect(Collectors.toMap(obj -> obj.getCode(), v -> v)); CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap); return (E) unmodifiableMap.get(type); } return (E) enumMap.get(type); } }
自定義的反序列化類
package com.common.config; import com.common.interfaces.Dict; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; /** * 枚舉反序列化器 */ public class DictEnumDeserializer extends StdDeserializer<Dict> implements ContextualDeserializer { public DictEnumDeserializer() { super((JavaType) null); } public DictEnumDeserializer(JavaType valueType) { super(valueType); } @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) { return new DictEnumDeserializer(property.getType()); } @Override @SuppressWarnings("all") public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return (Dict) EnumUtil.match((Class) _valueClass, p.getIntValue()); } }
注入到spring中
注入方式1
由于項(xiàng)目中還配置有時間的序列化所以就把它們放一起了。建議使用的,把所有的序列化反序列化的都放這。
package com.cnpc.common.config; import com.common.interfaces.Dict; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleDeserializers; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.ClassKey; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Objects; /** * LocalDateTime配置 */ @Configuration // public class LocalDateTimeFormatConfig { 改了一個名字 public class WebCustomerConfig { private static final String DEFAULT_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd"; private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss"; // 這里加@Primary,在項(xiàng)目啟動的時候一共會加載12個convert,第7個是MappingJackson2HttpMessageConverter // 處理json映射對象參數(shù)的類,會采用這個ObjectMapper,默認(rèn)的是 new ObjectMapper();這里自己增加了一些序列化處理的方法 @Bean @Primary public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); // 注冊時間的序列化和反序列化處理 JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN))); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))); javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN))); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))); javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))); objectMapper.registerModule(javaTimeModule); //忽略識別不了的屬性 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 注冊枚舉的處理序列化和反序列的方式 SimpleModule sm = new SimpleModule(); //自定義查找規(guī)則 sm.setDeserializers(new SimpleDeserializers() { @Override public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc); if (enumDeserializer != null) { return enumDeserializer; } //遍歷枚舉實(shí)現(xiàn)的接口, 查找反序列化器 for (Class typeInterface : type.getInterfaces()) { // 如果實(shí)現(xiàn)了Dict接口但是沒有code值的就返回null,如果項(xiàng)目比較規(guī)范都有code值,或者沒有code值的,沒有實(shí)現(xiàn)Dict接口,可以去掉下面這個代碼 if (typeInterface.equals(Dict.class) ){ Dict[] enumConstants = (Dict[]) type.getEnumConstants(); if (Objects.isNull(enumConstants[0].getCode())) { return null; } } enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); if (enumDeserializer != null) { return enumDeserializer; } } return null; } }); sm.addDeserializer(Dict.class, new TypeEnumDeserializer()); // 增加枚舉的序列化方式 sm.addSerializer(Dict.class, new JsonSerializer<Dict>() { @Override public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 相當(dāng)于在枚舉類上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返給頁面的是一個對象code和label都返回 // gen.writeStartObject(); // gen.writeNumberField("code", value.getCode()); // gen.writeStringField("label", value.getLabel()); // gen.writeEndObject(); // 相當(dāng)于在枚舉code字段上加 @JsonValue 返回給頁面一個code值 gen.writeNumber(value.getCode()); } @Override public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING)); serialize(value, gen, serializers); typeSer.writeTypeSuffix(gen, typeIdDef); } }); objectMapper.registerModule(sm); return objectMapper; } }
注入方式2
下面這種注入會影響項(xiàng)目已經(jīng)有配置ObjectMapper的地方,建議都放一起。如果采用下面這種方式,需要自己創(chuàng)建一個MappingJackson2HttpMessageConverter,并將converter添加到list的第一個,如果添加到最后一個,他會先找list中前邊的MappingJackson2HttpMessageConverter,大約是第7個位置,就不會用自己寫的。
package com.common.config; import com.common.interfaces.Dict; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.module.SimpleDeserializers; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.ClassKey; import lombok.extern.slf4j.Slf4j; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; /** * 裝載枚舉序列化器 */ @Configuration @Slf4j public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapperForWebConvert()); converters.add(0, stringHttpMessageConverter); // 將這個MappingJackson2HttpMessageConverter添加到第一個,是為了優(yōu)先找到,否則就有其他MappingJackson2HttpMessageConverter去處理了 converters.add(0, converter); } // 這個沒有時間的處理配置。 public ObjectMapper objectMapperForWebConvert() { ObjectMapper om = new ObjectMapper(); om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); SimpleModule sm = new SimpleModule(); //自定義查找規(guī)則 sm.setDeserializers(new SimpleDeserializers() { @Override public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc); if (enumDeserializer != null) { return enumDeserializer; } //遍歷枚舉實(shí)現(xiàn)的接口, 查找反序列化器 for (Class typeInterface : type.getInterfaces()) { // 如果實(shí)現(xiàn)了Dict接口但是沒有code值的就返回null,如果項(xiàng)目比較規(guī)范都有code值,或者沒有code值的,沒有實(shí)現(xiàn)Dict接口,可以去掉下面這個代碼 if (typeInterface.equals(Dict.class)){ Dict[] enumConstants = (Dict[]) type.getEnumConstants(); if (Objects.isNull(enumConstants[0].getCode())) { return null; } } // 這里的classKey不要導(dǎo)錯包,必須是com.fasterxml.jackson.databind.type.ClassKey,否則會找不到,導(dǎo)致自定義的反序列化類不起作用 enumDeserializer = this._classMappings.get(new ClassKey(typeInterface)); if (enumDeserializer != null) { return enumDeserializer; } // 上邊的ClassKey導(dǎo)入包錯誤,找不到臨時加的 // if (typeInterface.equals(Dict.class)){ // return new DictEnumDeserializer(); // } // return new DictEnumDeserializer(); } return null; } }); // 添加枚舉反序列化處理器 sm.addDeserializer(Dict.class, new DictEnumDeserializer()); sm.addSerializer(Dict.class, new JsonSerializer<Dict>() { @Override public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 相當(dāng)于在枚舉類上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返給頁面的是一個對象code和label都返回 // gen.writeStartObject(); // gen.writeNumberField("code", value.getCode()); // gen.writeStringField("label", value.getLabel()); // gen.writeEndObject(); // 相當(dāng)于在枚舉code字段上加 @JsonValue 返回給頁面一個code值 gen.writeNumber(value.getCode()); } @Override public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING)); serialize(value, gen, serializers); typeSer.writeTypeSuffix(gen, typeIdDef); } }); om.registerModule(sm); return om; } }
這樣就可以測試了。沒有實(shí)現(xiàn)Dict接口的枚舉會采用下一個MappingJackson2HttpMessageConverter的new ObjectMapper()去處理,按照默認(rèn)的去處理
斷點(diǎn)測試
第一次賦值
加載自定義的ObjectMapper
現(xiàn)在converter變成了14個了,因?yàn)檫@是注入方式2,自己往里面放了2個,
請求訪問時,斷點(diǎn)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
注入方式1斷點(diǎn)測試
啟動過程中會放入2個MappingJackson,會看到第7個下移到第8個了
注入方式2斷點(diǎn)測試
第一次啟動地址是15536
走到自己寫的枚舉處理
改進(jìn)枚舉工具類
改進(jìn)之后,即可已返回給前端枚舉對象,也可以接收對象類型的,也可以接收數(shù)值,也可以接收字符串
package com.common.config; import com.common.interfaces.Dict; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; /** * 枚舉反序列化器 */ public class DictEnumDeserializer extends StdDeserializer<Dict> implements ContextualDeserializer { public DictEnumDeserializer() { super((JavaType) null); } public DictEnumDeserializer(JavaType valueType) { super(valueType); } @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) { return new DictEnumDeserializer(property.getType()); } // 自定義的反序列化器這個方法,需要傳JsonParser @Override @SuppressWarnings("all") public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return (Dict) EnumUtil.match((Class) _valueClass, p); } }
package com.common.config; import com.common.interfaces.Dict; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * 枚舉匹配工具類 * 改進(jìn)的枚舉工具 */ public class EnumUtil { private static final Map<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP = new ConcurrentHashMap<>(16); @SuppressWarnings("unchecked") public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, JsonParser jsonParser) throws IOException { Integer code = null; // 接收數(shù)值型的 if (jsonParser.currentToken() == JsonToken.VALUE_NUMBER_INT){ code = jsonParser.getValueAsInt(); // 接收OBJECT類型的參數(shù) } else if (jsonParser.currentToken() == JsonToken.START_OBJECT){ while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldname = jsonParser.getCurrentName(); if ("code".equals(fieldname)) { jsonParser.nextToken(); code = jsonParser.getValueAsInt(); break; } } // 接收字符串類型的,需要判斷是純數(shù)字 }else if (jsonParser.currentToken() == JsonToken.VALUE_STRING){ String codestr = jsonParser.getValueAsString(); if (codestr.matches("^[0-9]*$")) { code = Integer.valueOf(codestr); } } if (Objects.isNull(code)){ throw new RuntimeException("沒有code找不到對應(yīng)的枚舉"); } return getDictEnum(enumClass, code); } private static <E extends Enum<E> & Dict> E getDictEnum(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants()) .collect(Collectors.toMap(obj -> obj.getCode(), v -> v)); CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap); E e = (E) unmodifiableMap.get(code); if (Objects.isNull(e)){ throw new RuntimeException("指定類型code找不到對應(yīng)的枚舉code = " + code); } return e; } E e = (E) enumMap.get(code); if (Objects.isNull(e)){ throw new RuntimeException("指定類型code找不到對應(yīng)的枚舉code = " + code); } return e; } // 原來的方法,只能接收數(shù)值code /*public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants()) .collect(Collectors.toMap(Dict::getCode, v -> v)); CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap); return (E) unmodifiableMap.get(type); } return (E) enumMap.get(type); }*/ }
參考文章
https://developer.aliyun.com/article/979501
http://events.jianshu.io/p/33e537ea6f10
JsonParser的處理
https://blog.csdn.net/band_mmbx/article/details/126749515
到此這篇關(guān)于springboot序列化和反序列化器配置方法的文章就介紹到這了,更多相關(guān)springboot序列化和反序列化器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GC算法實(shí)現(xiàn)篇之并發(fā)標(biāo)記清除
這篇文章主要為大家介紹了GC算法實(shí)現(xiàn)篇之并發(fā)-標(biāo)記-清除,?CMS垃圾收集器在減少停頓時間上做了很多給力的工作,?大量的并發(fā)線程執(zhí)行的工作并不需要暫停應(yīng)用線程2022-01-01springboot+gradle 構(gòu)建多模塊項(xiàng)目的步驟
這篇文章主要介紹了springboot+gradle 構(gòu)建多模塊項(xiàng)目的步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過程
這篇文章主要介紹了SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03IDEA修改java文件后 不用重啟Tomcat服務(wù)便可實(shí)現(xiàn)自動更新
這篇文章主要介紹了IDEA修改java文件后 不用重啟Tomcat服務(wù)便可實(shí)現(xiàn)自動更新,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11解讀靜態(tài)資源訪問static-locations和static-path-pattern
本文主要介紹了Spring Boot中靜態(tài)資源的配置和訪問方式,包括靜態(tài)資源的默認(rèn)前綴、默認(rèn)地址、目錄結(jié)構(gòu)、訪問路徑以及靜態(tài)資源處理器的工作原理,通過配置文件和實(shí)現(xiàn)`WebMvcConfigurer`接口,可以自定義靜態(tài)資源目錄和訪問前綴2025-01-01