前言
Java8
推出了新的日期时间API,位于java.time
包下。在使用SpringBoot
搭建前后端分离的项目,提供Restful风格的Http接口时,肯定会遇到提供日期时间查询的需求。因为Spring
目前暂不支持自动转换为LocalDate
或LocalDataTime
,所以需要进行配置,以支持使用LocalDate
或LocalDataTime
来接收日期时间字段。
源码地址:https://github.com/zccodere/d...
项目演示
创建名为14-time
的maven
项目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);
}
}
执行TimeControllerTest
类plusOneDay()
测试方法,由于Spring
目前暂不支持自动转换为LocalDate
或LocalDataTime
,测试失败。
我们通过查看控制台日志,发现如下输出
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]]]
通过分析日志,找到原因是缺少转换字段类型为LocalDate
或LocalDataTime
的org.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.再次执行TimeControllerTest
类plusOneDay()
测试方法,测试通过。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。