Java8
推出了新的日期時間API,位於java.time
包下。在使用SpringBoot
搭建先後端分離的項目,提供Restful風格的Http接口時,確定會遇到提供日期時間查詢的需求。由於Spring
目前暫不支持自動轉換爲LocalDate
或LocalDataTime
,因此須要進行配置,以支持使用LocalDate
或LocalDataTime
來接收日期時間字段。java
源碼地址:https://github.com/zccodere/d...git
建立名爲14-time
的maven
項目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.編寫App
類web
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
類spring
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
類apache
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
類app
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
,測試失敗。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]]]
經過分析日誌,找到緣由是缺乏轉換字段類型爲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()
測試方法,測試經過。