springboot序列化和反序列化器配置方法
介紹
本文介紹在項目中時間類型、枚舉類型的序列化和反序列化自定義的處理類,也可以使用注解。
建議枚舉都實現(xiàn)一個統(tǒng)一的接口,方便處理。我這定義了一個Dict接口。
枚舉類型注解處理
這種方式比較靈活,可以讓枚舉按照自己的方式序列化,可以序列化code值(推薦),也可以序列化對象。序列化為對象:對于前端來說是比較好的,前端接收到code值和中文label,頁面顯示label就行了,但是對于后端,服務(wù)之間調(diào)用,使用feign調(diào)用的時候,序列化為對象,被調(diào)用服務(wù)的controller層就接收不到這個枚舉值了,因為接收的是枚舉序列化的對象,無法反序列化成枚舉對象了。除非使用自定義反序列加判斷去處理,比較麻煩。參考改進(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);
}
}實體類
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)上,如果中跳過了,接收值的時候就會報錯。比如上邊的枚舉類DeliverDateModelEnum,傳0不會報錯,傳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,所以又寫了一個字典解析類,將所以實現(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);
// 反射獲取所有實現(xiàn) DictInterface 接口的枚舉
Set<Class<? extends Dict>> monitorClasses = reflections.getSubTypesOf(Dict.class);
/*
* 封裝字典列表
* 過濾掉不是枚舉的實現(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是校驗有沒有重復(fù)的type, 如果存在重復(fù)的會報錯
.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;
/**
* 字典項列表
*/
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;
/**
* 字典項
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DictItemVO {
/**
* 編碼
*/
private String code;
/**
* 標(biāo)簽
*/
private String label;
}自定義枚舉類型處理(不建議使用)
這種方式只能支持單個屬性接收值,接收list枚舉,map。set接收枚舉都會報錯。
由于上邊規(guī)定所有的枚舉都需要實現(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á)式寫法會報錯
// .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
由于項目中還配置有時間的序列化所以就把它們放一起了。建議使用的,把所有的序列化反序列化的都放這。
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,在項目啟動的時候一共會加載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;
}
//遍歷枚舉實現(xiàn)的接口, 查找反序列化器
for (Class typeInterface : type.getInterfaces()) {
// 如果實現(xiàn)了Dict接口但是沒有code值的就返回null,如果項目比較規(guī)范都有code值,或者沒有code值的,沒有實現(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
下面這種注入會影響項目已經(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;
}
//遍歷枚舉實現(xiàn)的接口, 查找反序列化器
for (Class typeInterface : type.getInterfaces()) {
// 如果實現(xiàn)了Dict接口但是沒有code值的就返回null,如果項目比較規(guī)范都有code值,或者沒有code值的,沒有實現(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;
}
}這樣就可以測試了。沒有實現(xiàn)Dict接口的枚舉會采用下一個MappingJackson2HttpMessageConverter的new ObjectMapper()去處理,按照默認(rèn)的去處理
斷點測試

第一次賦值

加載自定義的ObjectMapper

現(xiàn)在converter變成了14個了,因為這是注入方式2,自己往里面放了2個,

請求訪問時,斷點
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)

注入方式1斷點測試

啟動過程中會放入2個MappingJackson,會看到第7個下移到第8個了

注入方式2斷點測試
第一次啟動地址是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算法實現(xiàn)篇之并發(fā)標(biāo)記清除
這篇文章主要為大家介紹了GC算法實現(xiàn)篇之并發(fā)-標(biāo)記-清除,?CMS垃圾收集器在減少停頓時間上做了很多給力的工作,?大量的并發(fā)線程執(zhí)行的工作并不需要暫停應(yīng)用線程2022-01-01
springboot+gradle 構(gòu)建多模塊項目的步驟
這篇文章主要介紹了springboot+gradle 構(gòu)建多模塊項目的步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過程
這篇文章主要介紹了SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03
IDEA修改java文件后 不用重啟Tomcat服務(wù)便可實現(xiàn)自動更新
這篇文章主要介紹了IDEA修改java文件后 不用重啟Tomcat服務(wù)便可實現(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)資源處理器的工作原理,通過配置文件和實現(xiàn)`WebMvcConfigurer`接口,可以自定義靜態(tài)資源目錄和訪問前綴2025-01-01

