当前位置: 首页 > news >正文

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

介绍

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

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

枚举类型注解处理

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

定义统一枚举接口

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值@JsonValueprivate 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 = "标题", 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;}
}
请求参数
{"id":11,"title": "dajf","deliverDateModel":2
}响应结果
{"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// 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉// @JsonValueprivate final Integer code;private final String label;}

结果

请求参数
{"id":11,"title": "dajf","deliverDateModel":0
}响应结果
{"id": 55,"title": "dajf","deliverDateModel": {"code": 0,"label": "相同时间"},"purchaseMethod": null
}

反序列化处理

这就是说,前端传code值0,后端可以对应到枚举字段上,默认的是使用下标ordinal来反序列化的,按照0,1,2,3...去对应上,如果中跳过了,接收值的时候就会报错。比如上边的枚举类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// 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉// @JsonValueprivate 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);}}

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

将枚举转字典存储

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;/*** 系统字典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;}/*** 启动时扫描*/@PostConstructpublic void initDict() {DICT_SET = scanDict();}/*** 扫描字典列表*/private Set<DictVO> scanDict() {// 反射要扫描的包路径Reflections reflections = new Reflections(scanPath);// 反射获取所有实现 DictInterface 接口的枚举Set<Class<? extends Dict>> monitorClasses = reflections.getSubTypesOf(Dict.class);/** 封装字典列表* 过滤掉不是枚举的实现* 反射调用枚举的 values 方法, 获取每个枚举内部的值列表*/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为默认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;})// 这里转map是校验有没有重复的type, 如果存在重复的会报错.collect(Collectors.toMap(item -> item, item -> item, (a1, a2) -> {throw new E("字典类型:" + a1.getType() + ", 有重复");})).keySet();}}
// controller层提供外部访问    
@ApiOperation("系统字典列表查询")@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;@Overridepublic 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);}@Overridepublic 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;/*** 标签*/private String label;}

自定义枚举类型处理

由于上边规定所有的枚举都需要实现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())// 这种表达式写法会报错//       .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);}@Overridepublic 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映射对象参数的类,会采用这个ObjectMapper,默认的是 new ObjectMapper();这里自己增加了一些序列化处理的方法@Bean@Primarypublic 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();//自定义查找规则sm.setDeserializers(new SimpleDeserializers() {@Overridepublic JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config,BeanDescription beanDesc) throws JsonMappingException {JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);if (enumDeserializer != null) {return enumDeserializer;}//遍历枚举实现的接口, 查找反序列化器for (Class typeInterface : type.getInterfaces()) {// 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现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>() {@Overridepublic void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {// 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
//                gen.writeStartObject();
//                gen.writeNumberField("code", value.getCode());
//                gen.writeStringField("label", value.getLabel());
//                gen.writeEndObject();// 相当于在枚举code字段上加 @JsonValue  返回给页面一个code值gen.writeNumber(value.getCode());}@Overridepublic 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

下面这种注入会影响项目已经有配置ObjectMapper的地方,建议都放一起。如果采用下面这种方式,需要自己创建一个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 {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();converter.setObjectMapper(objectMapperForWebConvert());converters.add(0, stringHttpMessageConverter);// 将这个MappingJackson2HttpMessageConverter添加到第一个,是为了优先找到,否则就有其他MappingJackson2HttpMessageConverter去处理了converters.add(0, converter);}// 这个没有时间的处理配置。public ObjectMapper objectMapperForWebConvert() {ObjectMapper om = new ObjectMapper();om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);SimpleModule sm = new SimpleModule();//自定义查找规则sm.setDeserializers(new SimpleDeserializers() {@Overridepublic JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config,BeanDescription beanDesc) throws JsonMappingException {JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);if (enumDeserializer != null) {return enumDeserializer;}//遍历枚举实现的接口, 查找反序列化器for (Class typeInterface : type.getInterfaces()) {// 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现Dict接口,可以去掉下面这个代码if (typeInterface.equals(Dict.class)){Dict[] enumConstants = (Dict[]) type.getEnumConstants();if (Objects.isNull(enumConstants[0].getCode())) {return null;}}// 这里的classKey不要导错包,必须是com.fasterxml.jackson.databind.type.ClassKey,否则会找不到,导致自定义的反序列化类不起作用enumDeserializer = this._classMappings.get(new ClassKey(typeInterface));if (enumDeserializer != null) {return enumDeserializer;}// 上边的ClassKey导入包错误,找不到临时加的
//                    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>() {@Overridepublic void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {// 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
//                gen.writeStartObject();
//                gen.writeNumberField("code", value.getCode());
//                gen.writeStringField("label", value.getLabel());
//                gen.writeEndObject();// 相当于在枚举code字段上加 @JsonValue  返回给页面一个code值gen.writeNumber(value.getCode());}@Overridepublic 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;}
}

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

断点测试

第一次赋值

加载自定义的ObjectMapper

现在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

走到自己写的枚举处理

改进枚举工具类

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

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);}@Overridepublic 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;/*** 枚举匹配工具类
* 改进的枚举工具*/
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;// 接收数值型的if (jsonParser.currentToken() == JsonToken.VALUE_NUMBER_INT){code = jsonParser.getValueAsInt();// 接收OBJECT类型的参数} 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;}}// 接收字符串类型的,需要判断是纯数字}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找不到对应的枚举");}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找不到对应的枚举code = " + code);}return e;}E e = (E) enumMap.get(code);if (Objects.isNull(e)){throw new RuntimeException("指定类型code找不到对应的枚举code = " + code);}return e;}// 原来的方法,只能接收数值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

相关文章:

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

介绍本文介绍在项目中时间类型、枚举类型的序列化和反序列化自定义的处理类&#xff0c;也可以使用注解。建议枚举都实现一个统一的接口&#xff0c;方便处理。我这定义了一个Dict接口。枚举类型注解处理这种方式比较灵活&#xff0c;可以让枚举按照自己的方式序列化&#xff0…...

c++11 标准模板(STL)(std::unordered_map)(九)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…...

Seay代码审计工具

一、简介Seay是基于C#语言开发的一款针对PHP代码安全性审计的系统&#xff0c;主要运行于Windows系统上。这款软件能够发现SQL注入、代码执行、命令执行、文件包含、文件上传、绕过转义防护、拒绝服务、XSS跨站、信息泄露、任意URL跳转等漏洞&#xff0c;基本上覆盖常见PHP漏洞…...

界面开发(4)--- PyQt5实现打开图像及视频播放功能

PyQt5创建打开图像及播放视频页面 上篇文章主要介绍了如何实现登录界面的账号密码注册及登录功能&#xff0c;还简单介绍了有关数据库的连接方法。这篇文章我们介绍一下如何在设计的页面中打开本地的图像&#xff0c;以及实现视频播放功能。 实现打开图像功能 为了便于记录实…...

核心系统国产平台迁移验证

核心系统国产平台迁移验证 摘要&#xff1a;信息技术应用创新&#xff0c;旨在实现信息技术领域的自主可控&#xff0c;保障国家信息安全。金融领域又是关系国家经济命脉的行业&#xff0c;而对核心交易系统的信息技术应用创新是交易所未来将要面临的重大挑战。为了推进国产化进…...

【数据结构之二叉树】——二叉树的概念及结构,特殊的二叉树和二叉树性质

文章目录一、二叉树的概念及结构1.概念2.现实中的二叉树3. 特殊的二叉树&#xff1a;3.二叉树的性质二、二叉树练习题总结一、二叉树的概念及结构 1.概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合: 或者为空由一个根节点加上两棵别称为左子树和右子树的二叉树组成…...

Android学习之帧动画和视图动画

帧动画 帧动画中的每一帧其实都是一张图片&#xff0c;将许多图片连起来播放&#xff0c;就形成了帧动画。 在drawable目录下新建frmae_animation文件&#xff0c;在这个文件中定义了帧动画的每一帧要显示的图片&#xff0c;播放时&#xff0c;按从上到下显示。 <?xml v…...

vue2和vue3的区别

这周呢主要就是整理整理学的东西&#xff0c;不然看的也记不住&#xff0c;把这些学的东西做成笔记&#xff0c;感觉会清楚许多&#xff0c;这次就把vue2和vue3的区别总结一下&#xff0c;明天要考四级&#xff0c;嗐&#xff0c;本来想着复习四级&#xff0c;结果只写了一两套…...

【你不知道的事】JavaScript 中用一种更先进的方式进行深拷贝:structuredClone

你是否知道&#xff0c;JavaScript中有一种原生的方法来做对象的深拷贝? 本文我们要介绍的是 structuredClone 函数&#xff0c;它是内置在 JavaScript 运行时中的: const calendarEvent {title: "Builder.io Conf",date: new Date(123),attendees: ["Steve…...

XE开发Linux应用(二)-Webservice

新建一个工程。选择如图。继续输入服务名然后就生成对应的单元。增加linux 平台。完善对应的单元代码{ Invokable implementation File for Txaliontest which implements Ixaliontest }unit xaliontestImpl;interfaceuses Soap.InvokeRegistry, System.Types, Soap.XSBuiltIns…...

kubernetes实战与源码学习

1.1 关于Kubernetes的介绍与核心对象概念 关于Kubernetes的介绍与核心对象概念-阿里云开发者社区 k8s架构 核心对象 使用kubeadm10分钟部署k8集群 使用 KuboardSpray 安装kubernetes_v1.23.1 | Kuboard k8s-上部署第一个应用程序 Deployment基本概念 给应用添加service&a…...

CNCF x Alibaba云原生技术公开课 第八章 应用配置管理

Pod配置管理分类 可变配置就用 ConfigMap&#xff1b;敏感信息是用 Secret&#xff1b;身份认证是用 ServiceAccount&#xff1b;资源配置是用 Resources&#xff1b;安全管控是用 SecurityContext&#xff1b;前置校验是用 InitContainers。 1、ConfigMap 概念&#xff1a;…...

YUV实践记录

文章目录YUV基础介绍&#xff1a;不同采样YUV格式的区别为什么要使用YUV格式呢&#xff1f;YUV的存储方式Android中的YUV_420_888附录&#xff1a;YUV基础介绍&#xff1a; YUV在做手机图像或者视频处理的时候会经常用到的一个格式&#xff0c;用此文来记录YUV相关介绍&#xf…...

【题解】百度2020校招Web前端工程师笔试卷(第一批):单选题、多选题

题目来源 若有错误请指正&#xff01; 单选 1 分页存储管理将进程的逻辑地址空间分成若干个页&#xff0c;并为各页加以编号&#xff0c;从0开始&#xff0c;若某一计算机主存按字节编址&#xff0c;逻辑地址和物理地址都是32位&#xff0c;页表项大小为4字节&#xff0c;若…...

探索云原生技术之容器编排引擎-kubeadm安装kubernetes1.21.10(新版:针对高版本内核)

❤️作者简介&#xff1a;2022新星计划第三季云原生与云计算赛道Top5&#x1f3c5;、华为云享专家&#x1f3c5;、云原生领域潜力新星&#x1f3c5; &#x1f49b;博客首页&#xff1a;C站个人主页&#x1f31e; &#x1f497;作者目的&#xff1a;如有错误请指正&#xff0c;将…...

2023广西自治区职业技能大赛“网络安全” 项目比赛任务书

2023广西自治区职业技能大赛“网络安全” 项目比赛任务书2023广西自治区职业技能大赛“网络安全” 项目比赛任务书A模块基础设施设置/安全加固&#xff08;200分&#xff09;A-1&#xff1a;登录安全加固&#xff08;Windows, Linux&#xff09;A-2&#xff1a;Nginx安全策略&a…...

Reactor模式

Reactor是一种设计模式&#xff0c;可以用于构建高并发的网络服务器。 Reactor模式的好处在于&#xff1a;可以在一个或多个reactor线程使用多路复用技术去管理所有网络连接连接建立、IO请求&#xff0c;保证工作线程不被IO阻塞。 前置知识&#xff1a;IO多路复用技术 1. 传统网…...

Git图解-IDEA中的Git操作

目录 一、配置Idea 二、项目克隆 三、文件状态识别 四、Git操作 4.1 git add--添加暂存区 4.2 git commit--提交本地仓库 4.3 git push--推送远程仓库 4.4 git pull--更新本地仓库 五、完整开发流程 5.1 步骤1&#xff1a;克隆项目 5.2 步骤2&#xff1a;创建自己开发…...

在一个web应用中应该如何完成资源的跳转

在一个web应用中通过两种方式&#xff0c;可以完成资源的跳转&#xff1a; 第一种方式&#xff1a;请求转发 第二种方式&#xff1a;重定向 转发和重定向的区别&#xff1a; 代码上的区别&#xff1a; 请求转发 // 获取请求转发器对象 RequestDispatcher dispatcher request.…...

前缀和部分题目

前缀和 前缀和指数组的前 N项之和&#xff0c;是个比较基础的算法 例题 面试题 17.05. 字母与数字 给定一个放有字母和数字的数组&#xff0c;找到最长的子数组&#xff0c;且包含的字母和数字的个数相同。 返回该子数组&#xff0c;若存在多个最长子数组&#xff0c;返回左…...

解决CLI工具接入DeepSeek API流式传输失败的实战指南

最近在对接DeepSeek的API时&#xff0c;遇到了一个挺典型的问题&#xff1a;用CLI工具调用API进行流式传输时&#xff0c;经常中途失败&#xff0c;返回的错误信息又比较模糊&#xff0c;调试起来很头疼。特别是在处理长文本生成或者需要连续对话的场景下&#xff0c;这个问题直…...

Cosmos-Reason1-7B保姆级教程:从NVIDIA模型下载到浏览器界面可用全流程

Cosmos-Reason1-7B保姆级教程&#xff1a;从NVIDIA模型下载到浏览器界面可用全流程 本文面向想要快速上手Cosmos-Reason1-7B推理工具的初学者&#xff0c;无需深厚技术背景&#xff0c;跟着步骤操作即可完成本地部署和使用。 1. 工具简介&#xff1a;你的本地推理助手 Cosmos-…...

【算法通关】递归:汉诺塔、合并链表、反转链表、两两交换、快速幂全解

文章目录1. 汉诺塔问题2. 合并两个有序链表3. 反转链表4. 两两交换链表中的节点5. 快速幂1. 汉诺塔问题 题目链接&#xff1a;汉诺塔问题 题目描述&#xff1a; 题解思路&#xff1a;递归 将 n 个盘子从 A 柱移到 C 柱&#xff08;以 A 为起点、C 为目标、B 为辅助&#xff…...

RePKG:解锁Wallpaper Engine壁纸资源的三大核心功能

RePKG&#xff1a;解锁Wallpaper Engine壁纸资源的三大核心功能 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg 你是否曾经看着Wallpaper Engine里精美的动态壁纸&#xff0c;想要提…...

Python工业视觉落地难?3个99%工程师忽略的部署断点及72小时解决方案

第一章&#xff1a;Python工业视觉落地难&#xff1f;3个99%工程师忽略的部署断点及72小时解决方案工业视觉项目在实验室中准确率高达99.8%&#xff0c;却在产线持续运行48小时后突然崩溃——这不是偶发故障&#xff0c;而是源于三个被长期忽视的部署断点&#xff1a;模型推理时…...

NaViL-9B效果对比图:同一图片下temperature=0与0.5响应差异

NaViL-9B效果对比图&#xff1a;同一图片下temperature0与0.5响应差异 1. 模型简介 NaViL-9B是由专业研究机构开发的原生多模态大语言模型&#xff0c;具备强大的文本理解和图像分析能力。该模型支持纯文本问答和图片理解两种主要功能&#xff0c;能够处理复杂的多模态任务。…...

深入解析Bluetooth AVDTP协议:音频/视频传输的核心机制

1. 蓝牙AVDTP协议初探&#xff1a;音频视频传输的幕后英雄 每次用蓝牙耳机听音乐或看视频时&#xff0c;你可能没意识到背后有个"隐形交通警察"在指挥数据流动。这个默默工作的角色就是AVDTP协议&#xff08;Audio/Video Distribution Transport Protocol&#xff09…...

OpenClaw内存优化方案:GLM-4.7-Flash在8GB设备运行

OpenClaw内存优化方案&#xff1a;GLM-4.7-Flash在8GB设备运行 1. 为什么需要内存优化 去年冬天&#xff0c;当我第一次尝试在旧款MacBook Pro&#xff08;8GB内存&#xff09;上运行GLM-4.7-Flash时&#xff0c;系统频繁卡顿甚至崩溃的经历让我记忆犹新。这促使我深入研究了…...

pdf2htmlEX色彩管理专家指南:高级色彩校准技术

pdf2htmlEX色彩管理专家指南&#xff1a;高级色彩校准技术 【免费下载链接】pdf2htmlEX Convert PDF to HTML without losing text or format. 项目地址: https://gitcode.com/gh_mirrors/pd/pdf2htmlEX 想要将PDF转换为HTML时保持完美的色彩还原吗&#xff1f;pdf2html…...

MiniCPM-V-2_6在Android应用开发中的实战:移动端AI集成指南

MiniCPM-V-2_6在Android应用开发中的实战&#xff1a;移动端AI集成指南 最近在捣鼓一个智能相册应用&#xff0c;想让它能自动识别照片里的内容&#xff0c;比如是猫是狗、是风景还是美食&#xff0c;然后智能分类。一开始想用云端的AI服务&#xff0c;但转念一想&#xff0c;…...