【问题标题】:How to use LocalDateTime RequestParam in Spring? I get "Failed to convert String to LocalDateTime"如何在 Spring 中使用 LocalDateTime RequestParam?我收到“无法将字符串转换为 LocalDateTime”
【发布时间】:2017-03-09 12:46:48
【问题描述】:

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

<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) {
//...
}

并使用此 URL 对其进行测试:

/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"
}

【问题讨论】:

    标签: spring spring-boot spring-mvc java-time jsr310


    【解决方案1】:

    TL;DR - 您可以仅使用 @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 存储库?这就是这个错误的实际问题开始的地方;/
    • 您也可以在签名方法中使用此方案:@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
    • @Anna 请发表您的评论作为答案,因为它应该是 imo 接受的一个
    • 谢谢,方法 2 对我有用,因为有时我会通过小步舞曲,而其他时候我也不需要。这只是照顾所有这些:)
    【解决方案2】:

    你做的一切都是正确的:)。 Here 是一个例子,它准确地展示了你在做什么。 只需@DateTimeFormat 注释您的 RequestParam。无需特殊的GenericConversionService 或控制器中的手动转换。 This 博客文章对此进行了描述。

    @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 {
    • 尽管我在上面发表了评论,但将注释从请求参数(实际上是其中两个:startDateendDate)移动到请求方法似乎会使该方法的行为变得更糟。
    • 这适用于没有时间戳的日期模式,但如果您在模式中包含时间戳,则无法将字符串转换为日期(或其他适用类型)。
    • 我错了,这适用于时间戳,但如果您复制粘贴 JavaDoc 中用于 org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME 的示例,它会失败。他们提供的示例应该使用 X 而不是 Z 作为其模式,因为它们包括 -05:00 而不是 -0500。
    • 我试过这个解决方案,如果你传递日期或日期时间,它可以工作,但是当值为 EMPTY 时,这会失败。
    【解决方案3】:

    我找到了解决方法here

    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) {
    // ...
    

    【讨论】:

    • 如何在这里捕获转换异常?
    • 这是最好的答案。即使日期字段是嵌套字段,它也可以工作。这也更好,因为这样您只需添加一次此配置。
    • 哇!浏览了一半的互联网才到这里...谢谢!
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      SpringBoot 2.X.X 及更新版本

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

      这已解决为 Spring Boot 问题 #9297answer 仍然有效且相关:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <version>2.0.0.RELEASE</version>
      </dependency>
      
      @RequestMapping(value = "datetime", method = RequestMethod.POST)
      public void foo(@RequestParam("dateTime") 
                      @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime ldt) {
          // IMPLEMENTATION
      }
      

      【讨论】:

        【解决方案6】:

        我遇到了同样的问题并找到了我的解决方案here(不使用注释)

        ...您至少必须在 [LocalDateTime] 转换器中正确注册一个字符串 您的上下文,以便 Spring 可以使用它自动执行此操作 每次您将字符串作为输入并期望 [LocalDateTime] 时。 (一个大的 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;
        

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

        【讨论】:

          【解决方案7】:

          以下适用于 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));
              }
          
          }
          

          【讨论】:

            【解决方案8】:

            上面的答案对我不起作用,但我在这里犯了一个错误:https://blog.codecentric.de/en/2017/08/parsing-of-localdate-query-parameters-in-spring-boot/ 获胜的 sn-p 是 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 );
                        }
                    } );
                }
            }
            

            【讨论】:

              【解决方案9】:

              您可以添加到配置中,此解决方案适用于可选参数和非可选参数。

              @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);
                          }
                      };
                  }
              
              

              【讨论】:

              • 以下是How do I write a good answer? 的一些指南。提供的这个答案可能是正确的,但它可以从解释中受益。仅代码答案不被视为“好”答案。
              【解决方案10】:

              这是另一种带有参数转换器的通用解决方案:

              import org.springframework.core.convert.converter.Converter;
              import org.springframework.stereotype.Component;
              import ru.diasoft.micro.msamiddleoffice.ftcaa.customerprofile.config.JacksonConfig;
              
              import java.time.DateTimeException;
              import java.time.LocalDateTime;
              import java.time.format.DateTimeFormatter;
              import java.time.format.DateTimeParseException;
              import java.util.Arrays;
              import java.util.List;
              import java.util.stream.Collectors;
              
              @Component
              public class LocalDateTimeConverter implements Converter<String, LocalDateTime>{
              
                  private static final List<String> SUPPORTED_FORMATS = Arrays.asList("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "[another date time format ...]");
                  private static final List<DateTimeFormatter> DATE_TIME_FORMATTERS = SUPPORTED_FORMATS
                          .stream()
                          .map(DateTimeFormatter::ofPattern)
                          .collect(Collectors.toList());
              
                  @Override
                  public LocalDateTime convert(String s) {
              
                      for (DateTimeFormatter dateTimeFormatter : DATE_TIME_FORMATTERS) {
                          try {
                              return LocalDateTime.parse(s, dateTimeFormatter);
                          } catch (DateTimeParseException ex) {
                              // deliberate empty block so that all parsers run
                          }
                      }
              
                      throw new DateTimeException(String.format("unable to parse (%s) supported formats are %s",
                              s, String.join(", ", SUPPORTED_FORMATS)));
                  }
              }
              

              【讨论】:

                【解决方案11】:

                全局配置:

                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,还是相反?
                【解决方案12】:

                您可以在application properties 中全局配置日期时间格式。 喜欢:

                spring.mvc.format.date=yyyy-MM-dd

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

                spring.mvc.format.time=HH:mm:ss

                签入mavern:org.springframework.boot:spring-boot-autoconfigure:2.5.3

                【讨论】:

                  【解决方案13】:

                  我在相关上下文中遇到了类似的问题

                  我正在使用 WebRequestDataBinder 将请求参数动态映射到模型。

                  Object domainObject = ModelManager.getEntity(entityName).newInstance();
                  WebRequestDataBinder binder = new WebRequestDataBinder(domainObject);
                  binder.bind(request);
                  

                  这段代码适用于原语,但不适用于 LocalDateTime 类型属性

                  为了解决这个问题,在调用 binder.bind 之前,我在调用 bind() 之前注册了一个自定义编辑器

                  binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport()
                                  {
                                      @Override
                                      public void setAsText(String text) throws IllegalArgumentException
                                      {
                                          setValue(LocalDateTime.parse(text, DateTimeFormatter.ISO_DATE_TIME));
                                      }
                  
                                      @Override
                                      public String getAsText() {
                                          return DateTimeFormatter.ISO_DATE_TIME.format((LocalDateTime) getValue());
                                      }
                  
                                  }
                              );
                  

                  这解决了问题。

                  【讨论】:

                    猜你喜欢
                    • 2019-11-27
                    • 1970-01-01
                    • 2019-08-07
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-01-20
                    • 1970-01-01
                    相关资源
                    最近更新 更多