Loading... 在JavaWeb程序的开发过程中,接口是前后端对接的主要窗口,而接口参数的接收有时候是一个令人头疼的事情,这其中最困扰程序猿的,应该是时间参数的接收。 比如:设置一个用户的过期时间,前端到底以什么格式传递参数呢?时间戳?还是2019-12-01 22:13:00这种格式?还是其他格式? 今天我就来总结一下SpringBoot Web应用接口接收时间类型参数的问题解决方案。 注:目前我对Spring源码的掌握还不是很好,所以这一篇仅仅总结一下解决方法,后面感悟多了会重写一下!:sunglasses: 示例代码请前往:[https://github.com/laolunsi/spring-boot-examples](https://github.com/laolunsi/spring-boot-examples) --- 经过简单的测试,我们知道: 1. 不使用@RequestBody注解的情况下,所有时间类型参数都会引起报错; 2. 使用@RequestBody,前端传递时间戳或`2019-11-22`形式正常,传递`2019-11-22 11:22:22`报错,其他格式同样报错。 之前有接触过类似的解决办法,在类的属性上加上@DateFormat注解,解决单个时间参数问题。 但是局限较多。 理想的解决方案是:一次配置,全局通用,多种格式,自动转换(朗朗上口嗷) ## 一、源码简要分析 首先我们来简单分析一下源码: ![file](https://upload-images.jianshu.io/upload_images/14256455-846c4bec26de45a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 深入的就不解释了(我现在也不懂) 简单来说,接口接收的参数,首先被HandlerMethodArgumentResolver的实现类处理了一遍,将其转换为我们需要的格式。 这里主要分为两种情况: 1. 使用了@RequestBody的参数,一般是对象接收,前端传递的通常是JSON形式 2. 其他接收参数的方式,比如@RequestAttribute,@RequestParam,或者默认形式,前端传递的通常是表单参数、请求URL后缀参数等 ## 二、解决方法 1. 默认形式,或使用@RequestAttribute,或使用@RequestParam,这样的参数,通过配置converter来解决问题 2. 使用@RequestBody解析的参数,通过在ObjectMapper中配置序列化和反序列化规则来处理 ## 2.1 自定义converter 针对第一种情况,我们需要配置converter,这里介绍两种方法: 1. @ControllerAdvice @InitBinder 2. 直接使用@Bean定义converter类 首先我们这里需要一个DateConverter类,这个类实现了Converter接口,重写了其中的convert方法,将String转成Date类型: 我们这里定义了三种处理格式: ```java /** * 日期转换类 * 将标准日期、标准日期时间、时间戳转换成Date类型 */ /*@Deprecated*/ public class DateConverter implements Converter<String, Date> { private Logger logger = LoggerFactory.getLogger(DateConverter.class); private static final String dateFormat = "yyyy-MM-dd HH:mm:ss"; private static final String shortDateFormat = "yyyy-MM-dd"; private static final String timeStampFormat = "^\\d $"; @Override public Date convert(String value) { logger.info("转换日期:" value); if(value == null || value.trim().equals("") || value.equalsIgnoreCase("null")) { return null; } value = value.trim(); try { if (value.contains("-")) { SimpleDateFormat formatter; if (value.contains(":")) { formatter = new SimpleDateFormat(dateFormat); } else { formatter = new SimpleDateFormat(shortDateFormat); } return formatter.parse(value); } else if (value.matches(timeStampFormat)) { Long lDate = new Long(value); return new Date(lDate); } } catch (Exception e) { throw new RuntimeException(String.format("parser %s to Date fail", value)); } throw new RuntimeException(String.format("parser %s to Date fail", value)); } } ``` 注:这个DateConverter类在下面都会用到。 ```java import com.aegis.yqmanagecenter.config.date.DateConverter; import com.aegis.yqmanagecenter.model.bean.common.JsonResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.http.HttpStatus; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import java.beans.PropertyEditorSupport; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @ControllerAdvice public class ControllerHandler { private Logger logger = LoggerFactory.getLogger(ControllerHandler.class); @InitBinder public void initBinder(WebDataBinder binder) { // 方法1,注册converter GenericConversionService genericConversionService = (GenericConversionService) binder.getConversionService(); if (genericConversionService != null) { genericConversionService.addConverter(new DateConverter()); } // 方法2,定义单格式的日期转换,可以通过替换格式,定义多个dateEditor,代码不够简洁 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); CustomDateEditor dateEditor = new CustomDateEditor(df, true); binder.registerCustomEditor(Date.class, dateEditor); // 方法3,同样注册converter binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) throws IllegalArgumentException { setValue(new DateConverter().convert(text)); } }); } } ``` 注:上面的三个方法都是利用@ControllerAdvice @InitBinder来设置时间参数处理的,其中1和3都可以设置DateConverter,而方法2只能一个一个手动设置格式。 这里需要注意,上述配置方法都无法解决Json格式数据中的时间参数接收问题。下面我们直接看完整的解决方案——将DateConverter注册为组件,并使用ObjectMapper来配置时间参数的序列化(接口返回值)和反序列化形式(接口接收参数)。 ## 2.2 配置ObjectMapper以及完整解决方案 完整的解决方案: ```java /** * 日期转换配置 * 解决@RequestAttribute、@RequestParam和@RequestBody三种类型的时间类型参数接收与转换问题 */ @Configuration public class DateConfig { /** * 默认日期时间格式 */ public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; /** * Date转换器,用于转换RequestParam和PathVariable参数 */ @Bean public Converter<String, Date> dateConverter() { return new DateConverter(); } /** * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json * 使用@RequestBody注解的对象中的Date类型将从这里被转换 */ @Bean public ObjectMapper objectMapper(){ ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); JavaTimeModule javaTimeModule = new JavaTimeModule(); //Date序列化和反序列化 javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() { @Override public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT); String formattedDate = formatter.format(date); jsonGenerator.writeString(formattedDate); } }); javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() { @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { return new DateConverter().convert(jsonParser.getText()); } }); objectMapper.registerModule(javaTimeModule); return objectMapper; } } ``` 参考:[简书-Spring中使用LocalDateTime、LocalDate等参数作为入参]( https://www.jianshu.com/p/b52db905f020 ) --- 补充: 不加任何配置的情况下: 1. post、get请求,单个参数,使用时间戳、2019-11-22, 2019-11-22 11:11:11这三种形式,全部报错`failed to convert value of type java.lang.String to required type java.util.Date` 2. post请求,使用对象接收参数,不使用@RequestBody,三种格式都报错: ``` properties Field error in object 'user' on field 'birthDate': rejected value [2019-11-22 11:11:11]; codes [typeMismatch.user.birthDate,typeMismatch.birthDate,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.birthDate,birthDate]; arguments []; default message [birthDate]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2019-11-22 11:11:11'; nested exception is java.lang.IllegalArgumentException]] ``` 3. post请求,后端使用@RequestBody,参数类型为application/json, 时间戳格式,传递成功 2019-11-22格式,传递成功 2019-11-22 11:11:22格式,报错: ```properties 2019-12-03 22:35:14.950 WARN 1240 --- [nio-8009-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2019-11-22 11:11:22": not a valid representation (error: Failed to parse Date value '2019-11-22 11:11:22': Cannot parse date "2019-11-22 11:11:22": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-11-22 11:11:22": not a valid representation (error: Failed to parse Date value '2019-11-22 11:11:22': Cannot parse date "2019-11-22 11:11:22": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null)) ``` 添加DateConverter,但不设置ObjectMapper时: 使用@RequestBody的接收不到参数,其他情况均可以处理三种格式的时间参数了 再配置ObjectMapper后: 以上所有类型的请求中的时间参数都可以被正确地转换为Date类型 Last modification:December 3, 2019 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 0 请作者喝杯肥宅快乐水吧!