SpringBoot 自定义对象映射器的使用

SpringBoot 底层默认使用 Jackson 进行 Java 实体对象与 Json 之间的转换,有时转换的效果并不是我们所期望的,需要进行额外的处理工作,有经验的小伙伴们,肯定遇到过下面两种典型的情况:

  • 当对象的属性是日期类型时,转换成 json 后的结果并不是我们想要的效果,还需要我们额外进行显示格式的处理。
  • 当对象的属性是 BigInteger、Long 等数字类型时,如果数字很大或者位数比较长的话,返回给前端页面时,js 获取数据后会丢失精度,这就会给我们造成不必要的麻烦,我们还需要将其转换成字符串后返回给前端使用。

为了能够统一进行处理以上问题,我们可以在 SpringBoot 中自定义对象映射器,这样就能够简化代码开发。下面我们还是通过代码来说明具体的实现细节吧,在博客的最后,会提供源代码的下载。


一、搭建工程

搭建一个 SpringBoot 的 web 工程,工程结果如下:

image

首先看一下 pom 文件,主要引入了 lombok 包和 mybatis-plus-core 包。

引入 lombok 包的目的是可以简化实体对象的创建,只需要编写属性,不需要写对应的 get 和 set 方法,另外 lombok 也提供了日志记录的功能,只要在具体的类上面使用 @Slf4j 注解,就可以使用 log.info 、log.error 等相关方法进行记录日志,非常方便。

引入 mybatis-plus-core 包的目的,是使用其内置的雪花算法生成 19 位数字,作为实体对象的 id 主键。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>compile</scope>
    </dependency>

    <!--引入 lombok 简化对象的创建,以及简化日志记录-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--引入 Mybatis plus core 使用其内置的雪花算法为对象实体生成主键-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-core</artifactId>
        <version>3.5.0</version>
    </dependency>
</dependencies>

对于 application.yml 的配置,这里只配置了启动端口为 8081

server:
  port: 8081

创建了一个 Employee 的实体类,用于承载数据。

package com.jobs.demo.entity;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class Employee implements Serializable {

    private Long id;

    private String name;

    //建议日期时间,使用 LocalDateTime,不要使用 Date
    private LocalDateTime createTime;
}

为了方便演示,这里在 resources 的 static 目录下放置了静态页面,使用 Vue 和 ElementUI 进行开发,静态页面的具体细节不在这里展示,可以在本博客的最后面下载源代码进行查看。由于 SpringBoot 默认不支持静态资源的访问,因此必须配置静态资源的目录和访问路径,这里创建了一个 WebMvcConfig 的配置类进行配置和解决静态资源的访问,具体细节如下:

package com.jobs.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    //设置静态资源目录,以及访问地址映射
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }
}

为了接口返回结果的统一,自定义了一个 ResultVO 类,用于承载最终的结果数据,转换成 json 返回给前端。

package com.jobs.demo.common;

import lombok.Data;

@Data
public class ResultVO<T> {

    //状态码,1 表示成功,0 表示失败
    private Integer status;

    //状态消息
    private String msg;

    //返回的数据对象
    private T data;

    //快捷返回成功
    public static <T> ResultVO<T> success(T data) {
        ResultVO<T> result = new ResultVO<>();
        result.status = 1;
        result.data = data;
        result.msg = "success";
        return result;
    }

    //快捷返回失败
    public static <T> ResultVO<T> error(Integer status, String msg) {
        ResultVO<T> result = new ResultVO<>();
        result.status = status;
        result.msg = msg;
        return result;
    }
}

最后我们创建一个 EmployeeController 类,编写一个接口

package com.jobs.demo.controller;

import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.jobs.demo.common.ResultVO;
import com.jobs.demo.entity.Employee;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@Slf4j
@RequestMapping("/emp")
@RestController
public class EmployeeController {

    @GetMapping("/{name}")
    public ResultVO getEmployee(@PathVariable String name) {

        //Mybatis plus core 内置的类,自带雪花算法函数。
        DefaultIdentifierGenerator dig = new DefaultIdentifierGenerator();
        Long id = dig.nextId(new Object());

        //引入 lombok 包,在任何类上面使用 @Slf4j 注解后,就可以很方便的使用 log 对象的方法记录日志
        log.info("后台生成的员工id为:" + id);

        Employee emp = new Employee();
        emp.setId(id);
        emp.setName(name);
        emp.setCreateTime(LocalDateTime.now());

        return ResultVO.success(emp);
    }
}

二、验证项目搭建成果

启动 SpringBoot 程序,打开浏览器访问 localhost:8081/index.html 输入员工姓名,获取结果如下图所示:

image

从上图中执行效果可以发现:

  • Employee 对象的 id 属性是 Long 类型,使用雪花算法生成的 19 位数字作为值,后端接口转换成 json 是没有问题的,但是前端页面的 js 由于自身处理的精度不够,导致获取数据后展示的数字不正确,这是一个比较严重的问题,我们需要将 id 转换成字符串返回前端使用,才能解决此问题。
  • Employee 对象的 createtime 是 LocalDateTime 日期时间类型,转换成 json 后,变成了数组,前端使用起来不方便,我们需要转换成中国人习惯使用的标准格式才比较方便阅读和使用。

下面我们就在 SpringBoot 中自定义对象映射器,来统一解决这个问题。


三、自定义对象映射器

只需要 2 步即可实现:

1 首先创建一个自定义类 JacksonObjectMapper 代码如下:

package com.jobs.demo.common;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
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 java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {

        super();

        //遇到未知属性时,不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在时,进行兼容性处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class,
                        new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class,
                        new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class,
                        new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class,
                        new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class,
                        new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class,
                        new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册模块,添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

上面将 BigInteger 和 Long 类型自动转换成 String 类型,另外对 3 中日期时间类型进行了序列化和反序列化的格式处理。

2 在上面创建的 WebMvcConfig 类中添加配置信息

完整的 WebMvcConfig 类的代码如下所示:

package com.jobs.demo.config;

import com.jobs.demo.common.JacksonObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    //设置静态资源目录,以及访问地址映射
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }


    //扩展mvc框架的消息转换器
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter();
        //设置自定义对象转换器,springboot 底层使用 Jackson 将 Java 对象转为 json
        mc.setObjectMapper(new JacksonObjectMapper());
        //将自定义的消息转换器对象添加到 mvc 框架的转换器集合中,顺序要靠前,否则不会生效
        converters.add(0, mc);
    }
}

3 再进行测试验证结果

最后我们重新运行 SpringBoot 程序,访问 localhost:8081/index.html 输入员工姓名,获取结果如下图所示:

image


本博客的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_object_mapper.zip

posted @ 2023-04-13 20:32  乔京飞  阅读(11112)  评论(0编辑  收藏  举报