局部處理
-
入參
入參就是在Controller層的方法參數中使用到了Date、LocalDateTime去接收前端傳過來的時間參數,或者你是用對象接收,對象里面有Date、LocalDateTime這樣的屬性的。
-
出參
從后端返回到前端的數據中帶Date、LocalDateTime這樣的屬性的
入參局部處理用 @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
注解,出參用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
注解,
向下面這樣:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User{
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime localDateTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate localDate;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
private LocalTime localTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
}
需要注意以下幾點:
@DateTimeFormat
是Spring的注解,而@JsonFormat
是Jackson的注解,還有timezone = "GMT+8"
需要帶上,jackson在序列化時間時是按照國際標准時間GMT進行格式化的,而在國內默認時區使用的是CST時區,兩者相差8小時。@DateTimeFormat
的pattern屬性值指定的日期時間格式並不是將要轉換成的日期格式,這個指定的格式是和傳入的參數對應的,假如注解為:@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss")
,則傳入的參數應該是這樣的:2018/08/02 22:05:55
,否則會拋出異常。- 用
@DateTimeFormat
時,無論是Date、LocalDateTime、LocalDate、LocalTime
都可以用yyyy-MM-dd HH:mm:ss
,但是用@JsonFormat
時就不行,你可以多給框架數據,它可以不要,但是不能少給。
測試Controller:
@RestController
@RequestMapping("/my_test")
public class TestController {
@GetMapping("/users")
public User getUser(User user) {
// 入參測試
System.out.println(user);
// 出參測試
return User.builder()
.localDateTime(LocalDateTime.now())
.localDate(LocalDate.now())
.localTime(LocalTime.now())
.date(new Date())
.build();
}
}
效果如下:
全局處理
入參
處理Date類型的入參很簡單,這樣配置就行了:
Application.yml
# MVC 入參時間處理,不支持 Java8 時間
spring:
mvc:
date-format: 'yyyy-MM-dd HH:mm:ss'
想要支持 Java8 的 LocalDateTime就可以向下面這樣:
直接上代碼吧,原理就是使用 @InitBinder 和 @ControllerAdvice 注解實現,在每個Controller方法執行之前先執行 initBinder() 方法,將前端傳過來的時間字符串進行轉換后再綁定到參數上面。
想了解 @InitBinder 注解的看這里:https://www.cnblogs.com/lvbinbin2yujie/p/10459303.html
GlobalParamsHand.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.beans.PropertyEditorSupport;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/** * 全局參數處理 * <br/> * <p> * 創建人:LeiGQ <br> * 創建時間:2020-07-21 17:09 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> */
@Slf4j
@ControllerAdvice
public class GlobalParamsHand {
/** * The type Custom local date time editor. * * @author leiguoqing * @date 2020 -07-21 23:24:50 */
private static class CustomLocalDateTimeEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(text, formatter);
super.setValue(localDateTime);
}
}
/** * 后端接收 LocalDateTime 類型的參數,此方法會在每個Controller方法執行前執行,從而達到將前端傳來的字符串時間轉換為 LocalDateTime * <br/> * Date 類型轉換已在 Application.yml 中使用 Spring.mvc.date-format: 'yyyy-MM-dd HH:mm:ss'配置 * <br/> * 參考:<a href='https://www.jianshu.com/p/cb108ecbec89'>后端接收java.util.Date類型的參數</a> * * @param binder the binder * @author leiguoqing * @date 2020 -07-21 22:48:27 */
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注冊 CustomLocalDateTimeEditor
binder.registerCustomEditor(LocalDateTime.class, new CustomLocalDateTimeEditor());
}
}
出參
SpringBoot默認是使用Jackson來序列化的,我們就需要重新配置Jackson,像下面這樣:
我把出參的全局配置配置成LocalDateTime和Date均返回時間戳給前端,前端需要什么格式自己定,同時對科學記數法做了處理,如果你想改為類似 yyyy-MM-dd HH:mm:ss 這樣的格式,可以參考這個工具類里面改:https://blog.csdn.net/qq_34845394/article/details/90072563
先來看看下面這個有問題的配置,具體如下:
↓↓↓↓↓↓↓↓↓有問題的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓
JacksonConfig.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.LocalDateTime;
import java.util.TimeZone;
/** * ObjectMapper配置 */
@Configuration
public class JacksonConfig {
@Bean(name = "objMapper")
@Primary
public ObjectMapper getObjMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.configure() 方法與 objectMapper.disable(), objectMapper.enable() 作用一樣,
// 都是進行一些配置,查看源碼得知:都是調用 _serializationConfig.without(f) 方法
/*禁用一些配置*/
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
/*啟用一些配置*/
// 使科學計數法完整返回給前端
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
// 時間項目推薦返回前端時間戳,前端根據需要自己轉換格式
objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
/*時間模塊*/
JavaTimeModule javaTimeModule = new JavaTimeModule();
/*序列化配置, 針對java8 時間 項目推薦返回前端時間戳,前端根據需要自己轉換格式*/
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
/*注冊模塊*/
objectMapper.registerModule(javaTimeModule)
.registerModule(new Jdk8Module())
.registerModule(new ParameterNamesModule());
// 屬性命名策略
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
// 時區
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// Date 時間格式(非 java8 時間),也統一用時間戳,注釋掉
// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return objectMapper;
}
}
LocalDateTimeSerializer.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
/** * LocalDateTime 序列化 為時間戳 */
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
}
}
配置好Jackson之后,還需要把這個 objMapper 配置進 Springboot 的消息轉換器中,像下面這樣:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/** * 使用 JacksonConfig 中的 objMapper,兼容 java8 時間 * * @see JacksonConfig#getObjMapper() */
private final ObjectMapper objMapper;
// 構造注入
public MvcConfig(@Qualifier(value = "objMapper") ObjectMapper objMapper) {
this.objMapper = objMapper;
}
/** * 配置消息轉換器 * <br>創建人: leigq * <br>創建時間: 2018-11-14 16:29 * <br> * * @param converters 轉換器 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加映射 Jackson2 Http消息轉換器
converters.add(customJackson2HttpMessageConverter());
}
/** * 映射 Jackson2 Http消息轉換器 * 參考:https://www.cnblogs.com/anemptycup/p/11313481.html */
@Bean
public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objMapper);
return jsonConverter;
}
}
這樣我們返回給前端的屬性中有Date或LocalDateTime的就會轉成時間戳了。
↑↑↑↑↑↑↑↑↑↑↑↑↑↑有問題的配置↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
為什么有問題???原因如下:
當我們手動配置了ObjectMapper對象后,在application.yml中的jackson配置會失效,原因是我們手動注入了ObjectMapper對象,SpringBoot檢測到該對象已經存在,則不會再注入了,所以配置自然不會生效了,具體去看這個類源碼就知道了:JacksonHttpMessageConvertersConfiguration
那有沒有一種可以讓application.yml中的配置生效,我們只做配置增強的方法呢?答案是肯定的。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓完美的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
JacksonConfig.java
import com.blog.www.util.JacksonUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import java.util.TimeZone;
/** * ObjectMapper配置,在使用 ObjectMapper 的地方,方便直接注入 ObjectMapper 進行使用, * 但是推薦統一使用 {@link JacksonUtils} 工具類 * * @author leiguoqing * @date 2020 -07-22 21:03:08 */
@Configuration
public class JacksonConfig {
/** * jackson 2 ObjectMapper 構建定制器 * <br/> * 該段代碼並未覆蓋SpringBoot自動裝配的ObjectMapper對象,而是加強其配置. 詳情請參考: <a href='https://www.jianshu.com/p/68fce8b23341'>SpringBoot2.x下的ObjectMapper配置原理</a> * * @return the jackson 2 object mapper builder customizer * @author leiguoqing * @date 2020 -07-22 21:23:18 */
@Bean
public Jackson2ObjectMapperBuilderCustomizer customJackson() {
return jacksonObjectMapperBuilder -> {
//若POJO對象的屬性值為null,序列化時不進行顯示,暫時注釋掉,為空也顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
/*禁用一些配置, 已在 application.yml配置*/
// jacksonObjectMapperBuilder.failOnUnknownProperties(false);
/* 啟用一些配置 */
// 使科學計數法完整返回給前端,已在 application.yml配置
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
// 時間項目推薦返回前端時間戳,前端根據需要自己轉換格式, 已在 application.yml配置
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
/* 時間模塊 */
JavaTimeModule javaTimeModule = new JavaTimeModule();
/* 序列化配置, 針對java8 時間 項目推薦返回前端時間戳,前端根據需要自己轉換格式*/
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
/* 注冊模塊 */
jacksonObjectMapperBuilder.modules(javaTimeModule, new Jdk8Module(), new ParameterNamesModule());
// 屬性命名策略
jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
// 時區, 已在 application.yml配置
// jacksonObjectMapperBuilder.timeZone(TimeZone.getTimeZone("GMT+8"));
// 針對於Date類型,文本格式化,Date 時間格式(非 java8 時間),也統一用時間戳,注釋掉
// jacksonObjectMapperBuilder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
//-----------------------------------------------華麗的分割線---------------------------------------------------
/* ↓↓↓↓超級詳細的一些配置↓↓↓↓ */
//若POJO對象的屬性值為null,序列化時不進行顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
// //若POJO對象的屬性值為"",序列化時不進行顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
// //DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES相當於配置,JSON串含有未知字段時,反序列化依舊可以成功
// jacksonObjectMapperBuilder.failOnUnknownProperties(false);
// //序列化時的命名策略——駝峰命名法
// jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
// //針對於Date類型,文本格式化
// jacksonObjectMapperBuilder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
//
// //針對於JDK新時間類。序列化時帶有T的問題,自定義格式化字符串
// JavaTimeModule javaTimeModule = new JavaTimeModule();
// javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// jacksonObjectMapperBuilder.modules(javaTimeModule);
//
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// //默認關閉,將char[]數組序列化為String類型。若開啟后序列化為JSON數組。
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS);
//
// //默認開啟,若map的value為null,則不對map條目進行序列化。(已廢棄)。
// // 推薦使用:jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.WRITE_NULL_MAP_VALUES);
//
// //默認開啟,將Date類型序列化為數字時間戳(毫秒表示)。關閉后,序列化為文本表現形式(2019-10-23T01:58:58.308+0000)
// //若設置時間格式化。那么均輸出格式化的時間類型。
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// //默認關閉,在類上使用@JsonRootName(value="rootNode")注解時是否可以包裹Root元素。
// // (https://blog.csdn.net/blueheart20/article/details/52212221)
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRAP_ROOT_VALUE);
// //默認開啟:如果一個類沒有public的方法或屬性時,會導致序列化失敗。關閉后,會得到一個空JSON串。
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//
// //默認關閉,即以文本(ISO-8601)作為Key,開啟后,以時間戳作為Key
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
//
// //默認禁用,禁用情況下,需考慮WRITE_ENUMS_USING_TO_STRING配置。啟用后,ENUM序列化為數字
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_INDEX);
//
// //僅當WRITE_ENUMS_USING_INDEX為禁用時(默認禁用),該配置生效
// //默認關閉,枚舉類型序列化方式,默認情況下使用Enum.name()。開啟后,使用Enum.toString()。注:需重寫Enum的toString方法;
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
//
// //默認開啟,空Collection集合類型輸出空JSON串。關閉后取消顯示。(已過時)
// // 推薦使用serializationInclusion(JsonInclude.Include.NON_EMPTY);
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
//
// //默認關閉,當集合Collection或數組一個元素時返回:"list":["a"]。開啟后,"list":"a"
// //需要注意,和DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY 配套使用,要么都開啟,要么都關閉。
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
//
// //默認關閉。打開后BigDecimal序列化為文本。(已棄用),推薦使用JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN配置
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
// //默認關閉,即使用BigDecimal.toString()序列化。開啟后,使用BigDecimal.toPlainString序列化,不輸出科學計數法的值。
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
//
// /**
// * JsonGenerator.Feature的相關參數(JSON生成器)
// */
//
// //默認關閉,即序列化Number類型及子類為{"amount1":1.1}。開啟后,序列化為String類型,即{"amount1":"1.1"}
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
//
// /******
// * 反序列化
// */
// //默認關閉,當JSON字段為""(EMPTY_STRING)時,解析為普通的POJO對象拋出異常。開啟后,該POJO的屬性值為null。
// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// //默認關閉
//// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// //默認關閉,若POJO中不含有JSON中的屬性,則拋出異常。開啟后,不解析該字段,而不會拋出異常。
// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
};
}
}
LocalDateTimeSerializer.java 和上面的保持一樣。
application.yml
spring:
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ jackson 配置 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
## 自定義 WebMvcConfig 之后(添加 @EnableWebMvc 后),原有properties中的jackson配置會失效,所以必須在自定義實現類中再次對 jackson 的配置進行補充,
## 詳見:com.blog.www.web.config.MvcConfig 中配置的Jackson消息轉換器,原因是我們手動注入了ObjectMapper對象,SpringBoot檢測到該對象已經存在,則不會再注入了,所以配置自然不會生效了
## 配置參考:https://www.cnblogs.com/liaojie970/p/9396334.html
jackson:
# 反序列化
deserialization:
FAIL_ON_UNKNOWN_PROPERTIES: false
USE_BIG_DECIMAL_FOR_FLOATS: true
# 序列化
serialization:
# 使科學計數法完整返回給前端
WRITE_BIGDECIMAL_AS_PLAIN: true
# 時間項目推薦返回前端時間戳
WRITE_DATES_AS_TIMESTAMPS: true
# 日期格式化
date-format: 'yyyy-MM-dd HH:mm:ss'
time-zone: 'GMT+8'
像上面那樣配置就不會導致application.yml中的配置失效,我們可以看到,我們在application.yml中進行了簡單的配置,然后在JacksonConfig.java中對java8的時間做了處理(application.yml貌似不能配置模塊,所以只能用JavaConfig代碼配置了)。
需要注意的是,如果發現這樣配置不生效,那么很有可能是你在哪個位置加了個 @EnableWebMvc
注解,去掉該注解即可。
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑完美的配置↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
參考資料
非常感謝下面這個博主的文章,收益很大,要想變強,還是得學着看源碼,這樣就不會被被人寫錯的東西坑了:)