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

news2024/11/23 12:59:26

介绍

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

建议枚举都实现一个统一的接口,方便处理。我这定义了一个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值
    @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 = "标题", 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
    // 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
    // @JsonValue
    private 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
    // 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
    // @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);
    }

}

以上就是注解的枚举处理方法,统一定义接口后,为了前端可以通过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;
    }

    /**
     * 启动时扫描
     */
    @PostConstruct
    public 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;

    @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;

    /**
     * 标签
     */
    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);
    }

    @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映射对象参数的类,会采用这个ObjectMapper,默认的是 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();
        //自定义查找规则
        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;
                }


                //遍历枚举实现的接口, 查找反序列化器
                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>() {

            @Override
            public 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());
            }

            @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

下面这种注入会影响项目已经有配置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 {

    @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添加到第一个,是为了优先找到,否则就有其他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() {
            @Override
            public 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>() {

            @Override
            public 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());
            }

            @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;
    }
}

这样就可以测试了。没有实现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);
    }

    @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;

/**
 * 枚举匹配工具类
* 改进的枚举工具
 */
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/404384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三种通用方法——惠普电脑硬盘数据恢复

随着当今数字时代的到来&#xff0c;人们对于计算机硬盘上存储的数据越来越重视。然而&#xff0c;电脑硬盘也时常会发生数据丢失的情况&#xff0c;这时候就需要进行数据恢复。惠普电脑是市面上比较常见的品牌之一&#xff0c;因此本文将从惠普电脑硬盘数据恢复的角度出发&…

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

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

EXCEL技能点3-常用技能1

1 引用格式 公式中引用单元格或者区域时,引用的类型可分为以下三种: 绝对引用 相对引用 混合引用 在Excel里&#xff0c;每个单元格都有一个编码&#xff0c;就像人的身份证一样&#xff0c;在Excel里是按照行列进行编码&#xff0c;例如A1就是第一列的第一行。 那么我们想要引…

QEMU启动x86-Linux内核

目录QEMU简介linux启动流程我的环境安装QEMU软件包安装源码安装编译linux内核编译busybox制作initramfs使用QEMU启动linux内核简化命令参考QEMU简介 QEMU&#xff08;quick emulator&#xff09;是一个通用的、开源的硬件模拟器&#xff0c;可以模拟不同硬件架构&#xff08;如…

Docker入门 第四部分

Docker 的优势 Docker 相比于传统虚拟化方式具有更多的优势&#xff1a; Docker 启动快速属于秒级别。虚拟机通常需要几分钟去启动。 Docker 需要的资源更少。Docker 在操作系统级别进行虚拟化&#xff0c;Docker 容器和内核交互&#xff0c;几乎没有性能损耗&#xff0c;性能…

Windows安装Hadoop

当初搭建Hadoop、Hive、HBase、Flink等这些没有截图写文&#xff0c;今为分享特重装。下载Hadoop下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/以管理员身份运行cmd切换到所在目录执行start winrar x -y hadoop-3.3.4.tar.gz&#xff0c;解压。配置…

刚刚和ChatGPT聊了聊隐私计算

开放隐私计算ChatGPT最近太火了&#xff0c;作为一个背后有庞大数据支撑&#xff0c;而且还在不断进化的人工智能&#xff0c;每个人都想和它聊一聊。我们也不例外&#xff0c;于是刚刚和它聊了聊隐私计算那些事儿。先来几个行业问题&#xff0c;毕竟它背后有所有行业新闻、论文…

Python tkinter 如何实现网站下载工具?将所有数据一键获取

前言 铁汁们有没有想过&#xff0c;如何把几个代码的功能结合到一起呢&#xff1f; 有想过的话&#xff0c;有没有实现过呢&#xff1f; 其实很简单的啊&#xff0c;咱就写一个界面就好了&#xff0c;想要哪个代码运行&#xff0c;鼠标轻轻一点就行 开发环境 python 3.8: 解…

【分布式版本控制系统Git】| Git概述、Git安装、Git常用命令

目录 一&#xff1a;概述 1.1. 何为版本控制 1.2. 为什么需要版本控制 1.3. 版本控制工具 1.4. Git 简史 1.5. Git 工作机制 1.6. Git和代码托管中心 二&#xff1a;安装 2.1. Git安装 三&#xff1a;常用命令 3.1 设置用户签名 3.2 初始化本地库 3.3 查看本地库…

多节点部署一键启动脚本

1 背景 我们在实际项目开发的时候,往往一个大的项目需要很多人协同开发,大家都开发完成在一起联调的时候需要启动很多节点,如果每次都手动去启动所有节点效率非常低,所以我们可以写一个脚本一键启动所有进程,以下统称为节点。 根据节点依赖的环境,一键启动脚本分两种情…

电力电子中逐波限流控制以及dsp实现

逐波限流是指在电力系统运行中&#xff0c;对电力设备进行电流保护的一种措施。它的实现方式是通过对电力系统的电流进行逐波监测和控制&#xff0c;每一波电流都可以独立地进行限制&#xff0c;从而保护电力系统设备不受过载损坏或短路故障的影响。 逐波限流的作用是提高电力…

1. ELK Stack 理论篇之什么是ELK Stack?

ELK Stack 理论篇之什么是ELK Stack?1.1 什么是 ELK Stack&#xff1f;1.2 ELK Stack的发展史1.2.1 Elasticsearch1.2.2 引入 Logstash 和 Kibana&#xff0c;产品更强大1.2.3 社区越来越壮大&#xff0c;用例越来越丰富1.2.4 然后我们向 ELK 中加入了 Beats1.2.5 那么&#x…

es6模块

目录 一、语法说明 二、示例代码 三、运行测试 一.语法说明 JavaScript 现在有两种模块。一种是 ES6 模块&#xff0c;简称 ESM&#xff1b;另一种是 CommonJS 模块&#xff0c;简称 CJS。CommonJS 模块是 Node.js 专用的&#xff0c;与 ES6 模块不兼容。语法上面&#xff0…

XE开发Linux应用(二)-Webservice

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

Hive常用内置函数、窗口函数及自定义函数

文章目录一、字符串函数二、数值函数三、日期函数四、其余常用函数五、窗口函数5.1、语法5.2、常用窗口函数六、自定义函数6.1、自定义UDF函数6.2、自定义UDTF函数6.3、将自定义函数导入hive中6.3.1、将项目打成jar包6.3.2、将jar包传入hive目录6.3.3、在hive中加载jar包6.3.4、…

C++回顾(二十五)—— map/multimap容器

25.1 map/multimap的简介 map是标准的关联式容器&#xff0c;一个map是一个键值对序列&#xff0c;即(key,value)对。它提供基于key的快速检索能力。map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入&#xff0c;所以不能指定插入位置。map的…

BN,LN,IN,GN的理解和用法

绿色区域表示将该区域作用域(四种方法都贯穿了w,h维度)&#xff0c;即将该区域数值进行归一化&#xff0c;变为均值为0&#xff0c;标准差为1。BN的作用区域时N,W,H,表示一个batch数据的每一个通道均值为0&#xff0c;标准差为1&#xff1b;LN则是让每个数据的所有channel的均值…

新一代跨平台云备份工具Duplicacy

什么是 Duplicacy &#xff1f; Duplicacy 是一款云备份软件&#xff0c;通过 Duplicacy 可以将视频&#xff0c;图片&#xff0c;文件&#xff0c;注册表等数据备份到云端。Duplicacy 通过客户端加密和最高级别的重复数据删除功能&#xff0c;将您的文件备份到许多云存储。 安…

SQL查漏补缺

有这么一道题&#xff0c;先看题目&#xff0c;表的内容如下 显示GDP比非洲任何国家都要高的国家名称(一些国家的GDP值可能为NULL)。 错误的查询&#xff1a; SELECT name FROM bbcWHERE gdp > ALL (SELECT gdp FROM bbc WHERE region Africa)正确的查询&#xff1a; SE…

C++ 11 pair

class pair 可将两个 value视为一个单元。C标准库内多处用到了这个 class 。尤其是容器 map、multimap、unordered_map和 unordered_multimap就是使用 pair 来管理其以 key/value pair形式存在的元素。任何函数如果需要返回两个 value&#xff0c;也需要用到 pair&#xff0c;例…