J'utilise Spring Boot et inclus jackson-datatype-jsr310
avec Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.7.3</version>
</dependency>
Quand j'essaie d'utiliser un RequestParam avec un type Java 8 Date/Time),
@GetMapping("/test")
public Page<User> get(
@RequestParam(value = "start", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start) {
//...
}
et le tester avec cette URL:
/test?start=2016-10-8T00:00
Je reçois l'erreur suivante:
{
"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"
}
TL; DR - vous pouvez le capturer sous forme de chaîne avec seulement @RequestParam
, Ou vous pouvez également faire analyser par Spring la chaîne dans un Java date/classe de temps via @DateTimeFormat
sur le paramètre également.
le @RequestParam
est suffisant pour saisir la date que vous fournissez après le signe =, cependant, il entre dans la méthode en tant que String
. C'est pourquoi il jette l'exception de casting.
Il y a plusieurs façons d'y parvenir:
@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...
}
@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).
}
Vous avez tout fait correctement :). Here est un exemple qui montre exactement ce que vous faites. Juste Annotez votre RequestParam avec @DateTimeFormat
. Il n’est pas nécessaire de procéder à une conversion spéciale GenericConversionService
ou manuelle dans le contrôleur. Ceci blog écrit à ce sujet.
@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
}
}
Je suppose que vous avez eu un problème avec le format. Sur ma configuration, tout fonctionne bien.
Comme je l'ai mis dans le commentaire, vous pouvez également utiliser cette solution dans la méthode de signature: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
J'ai trouvé une solution de contournement ici .
Spring/Spring Boot prend uniquement en charge le format de date/date-heure dans les paramètres BODY.
Cette classe de configuration ajoute la prise en charge de la date/date-heure dans QUERY STRING:
@Configuration
public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}
Cela fonctionne même si vous liez plusieurs paramètres de requête à une classe (@DateTimeFormat
annotation impuissante dans ce cas):
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) {
// ...
J'ai rencontré le même problème et trouvé ma solution ici (sans utiliser les annotations)
... vous devez au moins enregistrer correctement une chaîne dans [LocalDateTime] Converter dans votre contexte, afin que Spring puisse l'utiliser pour le faire automatiquement à chaque fois que vous indiquez une chaîne en entrée et que vous attendez une [LocalDateTime]. (Un grand nombre de convertisseurs sont déjà implémentés par Spring et contenus dans le package core.convert.support, mais aucun n’implique une conversion [LocalDateTime])
Donc, dans votre cas, vous feriez ceci:
public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
public LocalDateTime convert(String source) {
DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
return LocalDateTime.parse(source, formatter);
}
}
et ensuite, enregistrez simplement votre haricot:
<bean class="com.mycompany.mypackage.StringToLocalDateTimeConverter"/>
avec des annotations
ajoutez-le à votre service de conversion:
@Component
public class SomeAmazingConversionService extends GenericConversionService {
public SomeAmazingConversionService() {
addConverter(new StringToLocalDateTimeConverter());
}
}
et enfin vous auriez alors @Autowire dans votre service de conversion:
@Autowired
private SomeAmazingConversionService someAmazingConversionService;
Vous pouvez en savoir plus sur les conversions avec spring (et le formatage) sur ce site . Soyez prévenu, il contient une tonne d’annonces, mais j’ai trouvé que c’était un site utile et une bonne introduction au sujet.
Suivre fonctionne bien avec Spring Boot 2.1.6:
contrôleur
@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);
}
}
classes 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;
}
Classe de test:
@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));
}
}
Les réponses ci-dessus n'ont pas fonctionné pour moi, mais j'ai gaffé sur l'une d'entre elles: https://blog.codecentric.de/fr/2017/08/parsing-of-localdate-query-parameters- in-spring-boot / L'extrait gagnant était l'annotation ControllerAdvice, ce qui présente l'avantage d'appliquer ce correctif à tous vos contrôleurs:
@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 );
}
} );
}
}