Как использовать LocalDateTime RequestParam в Spring? Я получаю "Не удалось преобразовать String в LocalDateTime"
Я использую Spring Boot и включаю jackson-datatype-jsr310
с Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.7.3</version>
</dependency>
Когда я пытаюсь использовать RequestParam с типом даты/времени Java 8,
@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"
}
Ответы
Ответ 1
TL; DR - вы можете захватить его как строку с помощью только @RequestParam
, или вы можете сделать так, чтобы Spring дополнительно проанализировал строку в классе даты/времени java с помощью @DateTimeFormat
для параметра.
@RequestParam
достаточно, чтобы получить дату, указанную вами после знака =, однако он входит в метод как String
. Вот почему он вызывает исключение приведения.
Есть несколько способов добиться этого:
- проанализируйте дату самостоятельно, взяв значение в виде строки.
@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...
}
- Используйте возможность 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).
}
Ответ 2
Вы все сделали правильно :). Вот пример, который показывает, что именно вы делаете. Просто @DateTimeFormat
свой RequestParam с помощью @DateTimeFormat
. Нет необходимости в специальном GenericConversionService
или ручном преобразовании в контроллере. Этот пост в блоге пишет об этом.
@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
}
}
Я думаю, у вас была проблема с форматом. На моей установке все работает хорошо.
Ответ 3
Как я добавил комментарий, вы также можете использовать это решение в методе подписи: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
Ответ 4
Я нашел обходной путь здесь.
Spring/Spring Boot поддерживает только формат даты/даты и времени в параметрах BODY.
Этот класс конфигурации добавляет поддержку даты/даты и времени в QUERY STRING:
@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) {
// ...
Ответ 5
Я столкнулся с той же проблемой и нашел свое решение здесь (без использования аннотаций)
... вы должны хотя бы правильно зарегистрировать строку в конвертере [LocalDateTime] в ваш контекст, чтобы Spring мог использовать его для автоматического выполнения этого для вы каждый раз, когда вы вводите String в качестве входных данных и ожидаете [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());
}
}
и, наконец, вы должны @Autowire в вашем ConversionService:
@Autowired
private SomeAmazingConversionService someAmazingConversionService;
Подробнее об конверсиях с Spring (и форматировании) можно прочитать на этом сайте. Будьте предупреждены, что у него масса рекламы, но я определенно нашел, что это полезный сайт и хорошее введение в эту тему.
Ответ 6
Следующее хорошо работает с 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);
}
}
Дто классы:
@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));
}
}
Ответ 7
Ответы выше не спомогли мне, но я наткнулся на один, который сработал здесь: 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 );
}
} );
}
}
Ответ 8
Вы можете добавить в конфигурацию, это решение работает как с необязательными, так и с необязательными параметрами.
@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);
}
};
}