使用LocalDate和LocalDateTime接收日期時間

前言

Java8推出了新的日期時間API,位於java.time包下。在使用SpringBoot搭建先後端分離的項目,提供Restful風格的Http接口時,確定會遇到提供日期時間查詢的需求。由於Spring目前暫不支持自動轉換爲LocalDateLocalDataTime,因此須要進行配置,以支持使用LocalDateLocalDataTime來接收日期時間字段。java

源碼地址:https://github.com/zccodere/d...git

項目演示

建立名爲14-timemaven項目pom文件以下github

<?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.編寫Appweb

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.編寫RequestBeanspring

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.編寫ResponseBeanapache

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.編寫AppTestSupportapp

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,測試失敗。dom

咱們經過查看控制檯日誌,發現以下輸出

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()測試方法,測試經過。

相關文章
相關標籤/搜索