【每日鲜蘑】SpringBoot中的@RequestParam在传递时间类型时发生的异常

28,775 阅读2分钟

SpringBoot中的@RequestParam在传递时间类型时发生异常,传入的时间无法被解析。我们一起研究一下如何在请求和应用程序级别上在Spring REST请求中接受DateLocalDateLocalDateTime参数。

一、异常举例

Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDate'; 
  nested exception is org.springframework.core.convert.ConversionFailedException.

二、举例复现

@RestController
public class DateTimeController {
 
    @PostMapping("/date")
    public void date(@RequestParam("date") Date date) {
        // ...
    }
 
    @PostMapping("/localdate")
    public void localDate(@RequestParam("localDate") LocalDate localDate) {
        // ...
    }
 
    @PostMapping("/localdatetime")
    public void dateTime(@RequestParam("localDateTime") LocalDateTime localDateTime) {
        // ...
    }
}

在调用上述接口的时候,就会复现异常。

即使我们在application.properties中配置了接受的时间格式也不会生效,因为这时不需要进行序列化处理,根本没有经过Jackson的处理。

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

三、解决方案

1. 使用注解 @DateTimeFormat

使用@DateTimeFormat直接指定接受的时间格式,即灵活又方便。

@PostMapping("/date")
public void date(@RequestParam("date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
    // ...
}

2. 使用@InitBinder

仅对当前的Controller起作用,不推荐。

@InitBinder
protected void initBinder(WebDataBinder binder) 
{
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    dateFormat.setLenient(false);
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}

3. 全局配置

可以参考Spring官方给出的方案,https://www.baeldung.com/spring-date-parameters 不推荐。

@Configuration
class DateTimeConfig {
 
    @Bean
    public FormattingConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        registrar.registerFormatters(conversionService);
        
        // 注册其他Formatters
        return conversionService;
    }
}

DateTimeFormatterRegistrar对象中手动注册LocalDateLocalDateTime的时间格式为yyyy-MM-dd"yyyy-MM-dd HH:mm:ss"

遗憾的是,并没有对Date进行处理,看源码,确实只是针对LocalDate进行的格式设置的。

/**
 * Set the formatter that will be used for objects representing date values.
 * <p>This formatter will be used for the {@link LocalDate} type.
 * When specified, the {@link #setDateStyle dateStyle} and
 * {@link #setUseIsoFormat useIsoFormat} properties will be ignored.
 * @param formatter the formatter to use
 * @see #setTimeFormatter
 * @see #setDateTimeFormatter
 */
public void setDateFormatter(DateTimeFormatter formatter) {
	this.formatters.put(Type.DATE, formatter);
}

四、扩展悦读

FormatterRegistrar 提供三种实现

三种实现
三种实现
  1. DateFormatterRegistrarSpring 3.2 版本之后针对Date, Calendar, long 这三种类型进行解析。如上第三种全局解决方案中,如果要对Date进行处理,那么就注册这个。

  2. DateTimeFormatterRegistrarSpring 4.0 版本之后针对JDK8的时间类型LocalDateLocalTimeLocalDateTime这三种格式进行解析,也就是第三种全局解决方案中注册的。

  3. JodaTimeFormatterRegistrarSpring 3.1 版本之后针对第三方Joda的实现,如果想要使用,那么需要引入Joda-Time的依赖。解析的类型是Joda中的时间类型,随着JDK8的广泛使用,已经很少使用Joda了。

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

本文使用 mdnice 排版