前言

Java8推出了新的日期时间API,位于java.time包下。在使用SpringBoot搭建前后端分离的项目,提供Restful风格的Http接口时,肯定会遇到提供日期时间查询的需求。因为Spring目前暂不支持自动转换为LocalDateLocalDataTime,所以需要进行配置,以支持使用LocalDateLocalDataTime来接收日期时间字段。

源码地址:https://github.com/zccodere/d...

项目演示

创建名为14-timemaven项目pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <artifactId>14-time</artifactId>

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

    <build>
        <plugins>
            <!-- 指定jdk -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

1.编写App

package com.zccoder.demo.time;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author zc 2019-08-30
 */
@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

2.编写RequestBean

package com.zccoder.demo.time.domain;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * 请求对象
 *
 * @author zc 2019-08-30
 */
public class RequestBean implements Serializable {

    @NotNull
    private LocalDate startDate;
    @NotNull
    private LocalDateTime startTime;

    @Override
    public String toString() {
        return "RequestBean{" +
                "startDate=" + startDate +
                ", startTime=" + startTime +
                '}';
    }

    public LocalDate getStartDate() {
        return startDate;
    }

    public void setStartDate(LocalDate startDate) {
        this.startDate = startDate;
    }

    public LocalDateTime getStartTime() {
        return startTime;
    }

    public void setStartTime(LocalDateTime startTime) {
        this.startTime = startTime;
    }
}

3.编写ResponseBean

package com.zccoder.demo.time.domain;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * 响应对象
 *
 * @author zc 2019-08-30
 */
public class ResponseBean implements Serializable {

    private LocalDate resultDate;
    private LocalDateTime resultTime;

    @Override
    public String toString() {
        return "ResponseBean{" +
                "resultDate=" + resultDate +
                ", resultTime=" + resultTime +
                '}';
    }

    public LocalDate getResultDate() {
        return resultDate;
    }

    public void setResultDate(LocalDate resultDate) {
        this.resultDate = resultDate;
    }

    public LocalDateTime getResultTime() {
        return resultTime;
    }

    public void setResultTime(LocalDateTime resultTime) {
        this.resultTime = resultTime;
    }
}

4.编写TimeController

package com.zccoder.demo.time.controller;

import com.zccoder.demo.time.domain.RequestBean;
import com.zccoder.demo.time.domain.ResponseBean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * 时间控制层
 *
 * @author zc 2019-08-30
 */
@Validated
@RestController
@RequestMapping("/time")
public class TimeController {

    /**
     * 增加一天
     *
     * @param requestBean 请求对象
     * @return 响应对象
     */
    @GetMapping
    public ResponseBean plusOneDay(@Valid RequestBean requestBean) {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setResultDate(requestBean.getStartDate().plusDays(1));
        responseBean.setResultTime(requestBean.getStartTime().plusDays(1));
        return responseBean;
    }
}

5.编写AppTestSupport

package com.zccoder.demo.time;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
 * 单元测试基类
 *
 * @author zc 2019-08-30
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public abstract class AppTestSupport {

    @Autowired
    private WebApplicationContext webApplicationContext;
    @Autowired
    protected ObjectMapper objectMapper;

    protected MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}

6.编写TimeControllerTest

package com.zccoder.demo.time.controller;

import com.fasterxml.jackson.core.type.TypeReference;
import com.zccoder.demo.time.AppTestSupport;
import com.zccoder.demo.time.domain.ResponseBean;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * 时间控制层测试类
 *
 * @author zc 2019-08-30
 */
public class TimeControllerTest extends AppTestSupport {

    /**
     * 增加一天
     */
    @Test
    public void plusOneDay() throws Exception {
        // 构建请求参数
        LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>(4);
        params.add("startDate", "2019-08-30");
        params.add("startTime", "2019-08-30 22:36:20");
        // 执行调用请求
        String response = mockMvc.perform(get("/time")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .params(params))
                .andExpect(status().isOk())
                .andReturn()
                .getResponse()
                .getContentAsString();
        TypeReference<ResponseBean> typeReference = new TypeReference<ResponseBean>() {
        };
        ResponseBean responseBean = objectMapper.readValue(response, typeReference);
        System.out.println(responseBean);
    }
}

执行TimeControllerTestplusOneDay()测试方法,由于Spring目前暂不支持自动转换为LocalDateLocalDataTime,测试失败。

我们通过查看控制台日志,发现如下输出

Field error in object 'requestBean' on field 'startDate': rejected value [2019-08-30]; codes [typeMismatch.requestBean.startDate,typeMismatch.startDate,typeMismatch.java.time.LocalDate,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestBean.startDate,startDate]; arguments []; default message [startDate]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.LocalDate' for property 'startDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.LocalDate] for value '2019-08-30'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2019-08-30]]
Field error in object 'requestBean' on field 'startTime': rejected value [2019-08-30 22:36:20]; codes [typeMismatch.requestBean.startTime,typeMismatch.startTime,typeMismatch.java.time.LocalDateTime,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestBean.startTime,startTime]; arguments []; default message [startTime]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.LocalDateTime' for property 'startTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.time.LocalDateTime] for value '2019-08-30 22:36:20'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2019-08-30 22:36:20]]]

通过分析日志,找到原因是缺少转换字段类型为LocalDateLocalDataTimeorg.springframework.format.Formatter

配置Formatter

1.编写LocalDateFormatter

package com.zccoder.demo.time.config;

import org.springframework.format.Formatter;

import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * 日期格式化
 *
 * @author zc 2019-08-30
 */
public class LocalDateFormatter implements Formatter<LocalDate> {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate parse(String text, Locale locale) throws ParseException {
        return LocalDate.parse(text, formatter);
    }

    @Override
    public String print(LocalDate localDate, Locale locale) {
        return formatter.format(localDate);
    }
}

2.编写LocalDateTimeFormatter

package com.zccoder.demo.time.config;

import org.springframework.format.Formatter;

import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * 日期时间格式化
 *
 * @author zc 2019-08-30
 */
public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public LocalDateTime parse(String text, Locale locale) throws ParseException {
        return LocalDateTime.parse(text, formatter);
    }

    @Override
    public String print(LocalDateTime localDateTime, Locale locale) {
        return formatter.format(localDateTime);
    }
}

3.编写MvcConfig

package com.zccoder.demo.time.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * MVC配置类
 *
 * @author zc 2019-08-30
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
        registry.addFormatterForFieldType(LocalDateTime.class, new LocalDateTimeFormatter());
    }
}

4.再次执行TimeControllerTestplusOneDay()测试方法,测试通过。


妙手空空
1.3k 声望370 粉丝

博观而约取,厚积而薄发


引用和评论

0 条评论