如何在Spring中使用LocalDateTime RequestParam?我得到“无法将字符串转换为LocalDateTime”


77

我使用Spring Boot并包含jackson-datatype-jsr310在Maven中:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.7.3</version>
</dependency>

当我尝试使用Java 8日期/时间类型的RequestParam时,

@GetMapping("/test")
public Page<User> get(
    @RequestParam(value = "start", required = false)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start) {
//...
}

并使用以下网址进行测试:

/test?start=2016-10-8T00:00

我收到以下错误:

{
  "timestamp": 1477528408379,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
  "message": "Failed to convert value of type [java.lang.String] to required type [java.time.LocalDateTime]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value '2016-10-8T00:00'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2016-10-8T00:00]",
  "path": "/test"
}

Answers:


56

TL; DR-您可以使用just捕获它为字符串@RequestParam,也可以让Spring也通过@DateTimeFormat参数另外将字符串解析为java日期/时间类。

@RequestParam足以抓住你等号(=)后提供的日期,但是,它涉及到的方法作为String。这就是为什么它引发强制转换异常。

有几种方法可以实现此目的:

  1. 自己解析日期,以字符串的形式获取值。
@GetMapping("/test")
public Page<User> get(@RequestParam(value="start", required = false) String start){

    //Create a DateTimeFormatter with your required format:
    DateTimeFormatter dateTimeFormat = 
            new DateTimeFormatter(DateTimeFormatter.BASIC_ISO_DATE);

    //Next parse the date from the @RequestParam, specifying the TO type as 
a TemporalQuery:
   LocalDateTime date = dateTimeFormat.parse(start, LocalDateTime::from);

    //Do the rest of your code...
}
  1. 利用Spring的自动解析和期望日期格式的能力:
@GetMapping("/test")
public void processDateTime(@RequestParam("start") 
                            @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) 
                            LocalDateTime date) {
        // The rest of your code (Spring already parsed the date).
}

可以,但是有一个主要问题-如果对于大多数请求,您可以使用Spring JPA存储库,为什么还要使用自定义控制器?这是此错误实际出现的地方; /
thorinkor

26
您也可以在签名方法中使用此解决方案:@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
Anna

1
@Anna请张贴作为回答您的评论,因为它应该是海事组织接受一个
华金属罗伯斯

谢谢,方法2对我有用,因为有时我会通过小块,而有时我也不需要。这只是照顾所有的事情:)
ASH

68

您所做的一切都正确:)。是一个显示您正在做什么的示例。只需使用注释您的RequestParam @DateTimeFormatGenericConversionService控制器中无需特殊或手动转换。这篇博客文章对此进行了介绍。

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "datetime", method = RequestMethod.POST)
    public void processDateTime(@RequestParam("datetime") 
                                @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
        //Do stuff
    }
}

我猜你格式有问题。在我的设置中,一切正常。


我接受了这个建议,它起作用了,但是后来我想知道注释是否可以应用于整个控制器方法……事实证明可以。但是,它不能应用于整个控制器@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface DateTimeFormat {
AbuNassar

尽管上面有我的评论,但是将注释从请求参数(其中两个,实际上是:startDateendDate)移动到请求方法似乎使方法的行为更糟​​。
AbuNassar

这对于没有时间戳的日期模式效果很好,但是如果您在模式中包含时间戳,它将无法将String转换为Date(或其他适用的类型)。
MattWeiler19年

我错了,这可以与时间戳配合使用,但是如果您将JavaDoc中的示例复制粘贴到org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME,它将失败。他们提供的示例的模式应该使用X而不是Z,因为它们包括-05:00而不是-0500。
MattWeiler19年

我尝试了此解决方案,并且如果您通过了date或DateTime,它会起作用,但是当值是EMPTY时,这将失败。
darshgohel

28

我在这里找到了解决方法。

Spring / Spring Boot仅在BODY参数中支持日期/日期时间格式。

以下配置类在QUERY STRING(请求参数)中添加了对日期/日期时间的支持:

// Since Spring Framwork 5.0 & Java 8+
@Configuration
public class DateTimeFormatConfiguration implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

分别:

// Until Spring Framwork 4.+
@Configuration
public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

即使您将多个请求参数绑定到某个类(@DateTimeFormat在这种情况下,注释也无能为力),它仍然有效:

public class ReportRequest {
    private LocalDate from;
    private LocalDate to;

    public LocalDate getFrom() {
        return from;
    }

    public void setFrom(LocalDate from) {
        this.from = from;
    }

    public LocalDate getTo() {
        return to;
    }

    public void setTo(LocalDate to) {
        this.to = to;
    }
}

// ...

@GetMapping("/api/report")
public void getReport(ReportRequest request) {
// ...

如何在这里捕捉转换异常?

这是最好的答案。即使“日期”字段是嵌套字段,它也可以工作。这也更好,因为您只需要添加一次此配置。
bluelurker

18

就像我在评论中一样,您也可以在签名方法中使用以下解决方案: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start


3

我遇到了同样的问题,并在这里找到了解决方案(未使用注释)

...您至少必须在上下文中正确地将字符串注册到[LocalDateTime]转换器,以便每当您将String作为输入并期望使用[LocalDateTime]时,Spring都可以使用它自动为您执行此操作。(Spring已经实现了很多转换器,它们都包含在core.convert.support包中,但是没有一个转换器涉及[LocalDateTime]转换)

因此,在您的情况下,您可以这样做:

public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
    public LocalDateTime convert(String source) {
        DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
        return LocalDateTime.parse(source, formatter);
    }
}

然后注册您的bean:

<bean class="com.mycompany.mypackage.StringToLocalDateTimeConverter"/>

带注释

将其添加到您的ConversionService中:

@Component
public class SomeAmazingConversionService extends GenericConversionService {

    public SomeAmazingConversionService() {
        addConverter(new StringToLocalDateTimeConverter());
    }

}

最后,您将在ConversionService中使用@Autowire:

@Autowired
private SomeAmazingConversionService someAmazingConversionService;

您可以在该网站上阅读有关spring(和格式)转换的更多信息。请注意,它有大量的广告,但是我绝对发现它是一个有用的网站,并且是该主题的很好的介绍。


2

以下内容在Spring Boot 2.1.6中运行良好:

控制者

@Slf4j
@RestController
public class RequestController {

    @GetMapping
    public String test(RequestParameter param) {
        log.info("Called services with parameter: " + param);
        LocalDateTime dateTime = param.getCreated().plus(10, ChronoUnit.YEARS);
        LocalDate date = param.getCreatedDate().plus(10, ChronoUnit.YEARS);

        String result = "DATE_TIME: " + dateTime + "<br /> DATE: " + date;
        return result;
    }

    @PostMapping
    public LocalDate post(@RequestBody PostBody body) {
        log.info("Posted body: " + body);
        return body.getDate().plus(10, ChronoUnit.YEARS);
    }
}

Dto类:

@Value
public class RequestParameter {
    @DateTimeFormat(iso = DATE_TIME)
    LocalDateTime created;

    @DateTimeFormat(iso = DATE)
    LocalDate createdDate;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostBody {
    LocalDate date;
}

测试类别:

@RunWith(SpringRunner.class)
@WebMvcTest(RequestController.class)
public class RequestControllerTest {

    @Autowired MockMvc mvc;
    @Autowired ObjectMapper mapper;

    @Test
    public void testWsCall() throws Exception {
        String pDate        = "2019-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime = "2029-05-01T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate))
          .andExpect(status().isOk())
          .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDateTime);
    }

    @Test
    public void testMapper() throws Exception {
        String pDate        = "2019-05-01";
        String eDate        = "2029-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime    = eDate + "T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate)
        )
        .andExpect(status().isOk())
        .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDate).contains(eDateTime);
    }


    @Test
    public void testPost() throws Exception {
        LocalDate testDate = LocalDate.of(2015, Month.JANUARY, 1);

        PostBody body = PostBody.builder().date(testDate).build();
        String request = mapper.writeValueAsString(body);

        MvcResult result = mvc.perform(MockMvcRequestBuilders.post("")
            .content(request).contentType(APPLICATION_JSON_VALUE)
        )
        .andExpect(status().isOk())
        .andReturn();

        ObjectReader reader = mapper.reader().forType(LocalDate.class);
        LocalDate payload = reader.readValue(result.getResponse().getContentAsString());
        assertThat(payload).isEqualTo(testDate.plus(10, ChronoUnit.YEARS));
    }

}

2

SpringBoot 2.XX更新

如果使用依赖项spring-boot-starter-web版本2.0.0.RELEASE或更高版本,则不再需要显式包括jackson-datatype-jsr310依赖项,该依赖项已spring-boot-starter-web通过提供spring-boot-starter-json

此问题已作为Spring Boot问题#9297解决,并且答案 仍然有效且相关:

@RequestMapping(value = "datetime", method = RequestMethod.POST)
public void foo(@RequestParam("dateTime") 
                @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime ldt) {
    // IMPLEMENTATION
}

1

上面的答案对我来说不起作用,但我错误地想到了一个在这里做的事情:https//blog.codecentric.de/en/2017/08/parsing-of-localdate-query-parameters-in-spring- boot / 获奖的代码片段是ControllerAdvice注释,它的优点是可以在所有控制器上应用此修复程序:

@ControllerAdvice
public class LocalDateTimeControllerAdvice
{

    @InitBinder
    public void initBinder( WebDataBinder binder )
    {
        binder.registerCustomEditor( LocalDateTime.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText( String text ) throws IllegalArgumentException
            {
                LocalDateTime.parse( text, DateTimeFormatter.ISO_DATE_TIME );
            }
        } );
    }
}

1

您可以添加到config,此解决方案可以使用可选参数以及非可选参数。

@Bean
    public Formatter<LocalDate> localDateFormatter() {
        return new Formatter<>() {
            @Override
            public LocalDate parse(String text, Locale locale) {
                return LocalDate.parse(text, DateTimeFormatter.ISO_DATE);
            }

            @Override
            public String print(LocalDate object, Locale locale) {
                return DateTimeFormatter.ISO_DATE.format(object);
            }
        };
    }


    @Bean
    public Formatter<LocalDateTime> localDateTimeFormatter() {
        return new Formatter<>() {
            @Override
            public LocalDateTime parse(String text, Locale locale) {
                return LocalDateTime.parse(text, DateTimeFormatter.ISO_DATE_TIME);
            }

            @Override
            public String print(LocalDateTime object, Locale locale) {
                return DateTimeFormatter.ISO_DATE_TIME.format(object);
            }
        };
    }


以下是一些有关如何写一个好的答案的准则。提供的答案可能是正确的,但可以从解释中受益。仅代码答案不视为“好”答案。
特伦顿·麦金尼

0

对于全局配置:

public class LocalDateTimePropertyEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(LocalDateTime.parse(text, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    }

}

然后

@ControllerAdvice
public class InitBinderHandler {

    @InitBinder
    public void initBinder(WebDataBinder binder) { 
        binder.registerCustomEditor(OffsetDateTime.class, new OffsetDateTimePropertyEditor());
    }

}

LocalDateTimePropertyEditor应该是OffsetDateTimePropertyEditor,反之亦然?
tom_mai78101
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.