大部分人在二三十岁上就死去了,因为过了这个年龄,他们只是自己的影子,此后的余生则是在模仿自己中度过,日复一日,更机械,更装腔作势地重复他们在有生之年的所作所为,所思所想,所爱所恨。——《约翰 • 克利斯朵夫》
我们在开发中对于日期,如果是协商好,请求发送指定格式的日期字符串
我们这边再转换成对应日期格式,如果每个都一个一个来转,非常麻烦,不方便
这里介绍一种全局的mvc
转换方式
使用@InitBinder
注解以及Jackson2ObjectMapperBuilderCustomizer
如果我们使用application/x-www-form-urlencoded
接参,则@InitBinder
上场了
只需要在Controller
中写下如下代码
public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_PATTERN = "yyyy-MM-dd"; public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); public static final ZoneId SYSTEM_DEFAULT_ZONE_ID = ZoneId.systemDefault(); /** * 我还没有写描述 * * @param binder 从Web请求参数到JavaBean对象的数据绑定的特殊DataBinder * @author <achao1441470436@gmail.com> * @date 2021/4/5 0005 0:48 */ @InitBinder protected void initBinder(WebDataBinder binder) { // Date 类型转换 binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override @SneakyThrows public void setAsText(String text) { setValue(textToDate(text)); } }); // LocalDate类型转换 binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(textToLocalDate(text)); } }); // LocalDateTime类型转换 binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(textToLocalDateTime(text)); } }); } /** * 时间字符串转Date * * @param text 2021-04-04 23:10:51 * @return java.util.Date|null * @author <achao1441470436@gmail.com> * @date 2021/4/4 0004 23:10 */ private Date textToDate(String text) { return Optional.ofNullable(textToLocalDateTime(text)).map(dateTime -> dateTime.atZone(SYSTEM_DEFAULT_ZONE_ID)).map(ZonedDateTime::toInstant).map(Date::from).orElse(null); } private LocalDate textToLocalDate(String text) { return Optional.ofNullable(text).filter(str -> !str.isEmpty()).map(str -> LocalDate.parse(str, DATE_FORMATTER)).orElse(null); } /** * 时间字符串转LocalDateTime * * @param text 2021-04-04 23:09:52 * @return java.time.ZonedDateTime|null * @author <achao1441470436@gmail.com> * @date 2021/4/4 0004 23:05 */ private LocalDateTime textToLocalDateTime(String text) { return Optional.ofNullable(text).filter(str -> !str.isEmpty()).map(str -> LocalDateTime.parse(str, DATE_TIME_FORMATTER)).orElse(null); }
然后你当前Controller
中所有Date
格式或者LocalDateTime
格式的参数都可以使用yyyy-MM-dd
格式传参啦
我们用下面这个实体类OrderPO
@Data @Builder @TableName("`order`") @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor public class OrderPO implements Serializable { private static final long serialVersionUID = 5494342842978779677L; /** * 编号 */ @TableId(type = IdType.INPUT) private Long id; /** * 发货时间 */ private Date sendTime; /** * 收货时间 */ private LocalDateTime receiveTime; /** * 保质期到期时间 */ private LocalDate dateProduction; }
写一个接口测试一下
@PostMapping("testDate") public AjaxJson testDate(OrderPO orderPO) { return AjaxJson.success().put("data", orderPO); }
我们发现成功传输
这里有个弊端,这个@InitBinder
只在当前Controller
生效
我们想要全部Controller
生效则需要写在@RestControllerAdvice
中
例如我们写一个全局日期转换处理器
package com.ruben.resolver; import lombok.SneakyThrows; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.swing.text.DateFormatter; import java.beans.PropertyEditorSupport; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Optional; /** * 全局日期转换处理器 * * @author <achao1441470436@gmail.com> * @date 2021/4/5 0005 0:45 */ @RestControllerAdvice public class GlobalTimeResolver { public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_PATTERN = "yyyy-MM-dd"; public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); public static final ZoneId SYSTEM_DEFAULT_ZONE_ID = ZoneId.systemDefault(); /** * 我还没有写描述 * * @param binder 从Web请求参数到JavaBean对象的数据绑定的特殊DataBinder * @author <achao1441470436@gmail.com> * @date 2021/4/5 0005 0:48 */ @InitBinder protected void initBinder(WebDataBinder binder) { // Date 类型转换 binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override @SneakyThrows public void setAsText(String text) { setValue(textToDate(text)); } }); // LocalDate类型转换 binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(textToLocalDate(text)); } }); // LocalDateTime类型转换 binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(textToLocalDateTime(text)); } }); } /** * 时间字符串转Date * * @param text 2021-04-04 23:10:51 * @return java.util.Date|null * @author <achao1441470436@gmail.com> * @date 2021/4/4 0004 23:10 */ private Date textToDate(String text) { return Optional.ofNullable(textToLocalDateTime(text)).map(dateTime -> dateTime.atZone(SYSTEM_DEFAULT_ZONE_ID)).map(ZonedDateTime::toInstant).map(Date::from).orElse(null); } private LocalDate textToLocalDate(String text) { return Optional.ofNullable(text).filter(str -> !str.isEmpty()).map(str -> LocalDate.parse(str, DATE_FORMATTER)).orElse(null); } /** * 时间字符串转LocalDateTime * * @param text 2021-04-04 23:09:52 * @return java.time.ZonedDateTime|null * @author <achao1441470436@gmail.com> * @date 2021/4/4 0004 23:05 */ private LocalDateTime textToLocalDateTime(String text) { return Optional.ofNullable(text).filter(str -> !str.isEmpty()).map(str -> LocalDateTime.parse(str, DATE_TIME_FORMATTER)).orElse(null); } }
之后我们发现确实所有Controller
都生效了
但有个问题,我们如果是@RequestBody
接参,用的是body
里的json
传输数据
那么这个就不管用了。。。
这个则需要在我们的某个@Configuration
中注入一个自定义的Jackson2ObjectMapperBuilderCustomizer
因为我们mvc
默认使用Jackson
序列化我们的参数
@Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { JavaTimeModule module = new JavaTimeModule(); module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(GlobalTimeResolver.DATE_TIME_PATTERN))); module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(GlobalTimeResolver.DATE_PATTERN))); module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(GlobalTimeResolver.DATE_TIME_PATTERN))); return builder -> { builder.simpleDateFormat(GlobalTimeResolver.DATE_TIME_PATTERN); // builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(GlobalTimeResolver.DATE_PATTERN))); // builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(GlobalTimeResolver.DATE_TIME_PATTERN))); builder.modules(module); }; }
我们再写个接口测试下
@PostMapping("changeOrder") public AjaxJson changeOrder(@RequestBody OrderPO order) { mpOrderMapper.updateById(order); return AjaxJson.success(); }
可以看到成功
注意这里有个坑啊
我们必须至少在JavaTimeModule
指定一个deserializer
再添加进去
否则本配置则失效。。。这个还是从源码看到的
今天踩到这个坑,弄到2021-04-05 01:45:43
有点无语
点进去modules
查看源码
/** * Specify one or more modules to be registered with the {@link ObjectMapper}. * Multiple invocations are not additive, the last one defines the modules to * register. * <p>Note: If this is set, no finding of modules is going to happen - not by * Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}). * As a consequence, specifying an empty list here will suppress any kind of * module detection. * <p>Specify either this or {@link #modulesToInstall}, not both. * @since 4.1.5 * @see #modules(List) * @see com.fasterxml.jackson.databind.Module */ public Jackson2ObjectMapperBuilder modules(Module... modules) { return modules(Arrays.asList(modules)); }
翻译过来
注意这句罪魁祸首
坑死人了!!!