Wie verwende ich LocalDateTime RequestParam im Frühjahr? Ich erhalte die Meldung "Fehler beim Konvertieren des Strings in LocalDateTime".

77

Ich benutze Spring Boot und in jackson-datatype-jsr310Maven enthalten:

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

Wenn ich versuche, ein RequestParam mit einem Java 8-Datums- / Uhrzeittyp zu verwenden,

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

und testen Sie es mit dieser URL:

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

Ich erhalte folgende Fehlermeldung:

{
  "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"
}
Kery Hu
quelle

Antworten:

56

TL; DR - Sie können es als Zeichenfolge mit just erfassen @RequestParamoder Spring kann die Zeichenfolge zusätzlich über @DateTimeFormatden Parameter in eine Java-Datums- / Zeitklasse analysieren .

das @RequestParamgenug ist , das Datum , das Sie liefern nach dem = -Zeichen zu packen, jedoch kommt es in dem Verfahren , wie ein String. Aus diesem Grund wird die Besetzungsausnahme ausgelöst.

Es gibt einige Möglichkeiten, dies zu erreichen:

  1. Analysieren Sie das Datum selbst und nehmen Sie den Wert als Zeichenfolge.
@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. Nutzen Sie die Fähigkeit von Spring, Datumsformate automatisch zu analysieren und zu erwarten:
@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).
}
Bwvolleyball
quelle
Sicher, aber es gibt ein großes Problem - warum einen benutzerdefinierten Controller verwenden, wenn Sie für die meisten dieser Anforderungen Spring JPA-Repositorys verwenden könnten? Und dies ist der Ort, an dem das eigentliche Problem mit diesem Fehler beginnt; /
Thorinkor
26
Sie können diese Lösung auch in der Signaturmethode verwenden:@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
Anna
1
@ Anna bitte posten Sie Ihren Kommentar als Antwort, da es der akzeptierte sein sollte imo
Joaquín L. Robles
Danke, Ansatz 2 funktioniert für mich, da ich manchmal Menuette passiere und manchmal auch nicht. das kümmert sich nur um all das :)
ASH
68

Du hast alles richtig gemacht :). Hier ist ein Beispiel, das genau zeigt, was Sie tun. Kommentieren Sie einfach Ihr RequestParam mit @DateTimeFormat. Es ist keine spezielle GenericConversionServiceoder manuelle Konvertierung in der Steuerung erforderlich . Dieser Blog-Beitrag schreibt darüber.

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

Ich denke, Sie hatten ein Problem mit dem Format. Bei meinem Setup funktioniert alles gut.

d0x
quelle
Ich habe diesen Rat befolgt und es hat funktioniert, aber dann habe ich mich gefragt, ob die Anmerkung auf die gesamte Controller-Methode angewendet werden kann ... und es stellt sich heraus, dass dies möglich ist. Es kann jedoch nicht auf den gesamten Controller angewendet werden : @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface DateTimeFormat {.
AbuNassar
Ungeachtet meines obigen Kommentars schien das Verschieben der Anmerkung von einem Anforderungsparameter (zwei davon tatsächlich: startDateund endDate) zur Anforderungsmethode das Verhalten der Methode zu verschlechtern.
AbuNassar
Dies funktioniert gut für Datumsmuster ohne Zeitstempel. Wenn Sie jedoch einen Zeitstempel in das Muster aufnehmen, kann der String nicht in ein Datum (oder einen anderen anwendbaren Typ) konvertiert werden.
MattWeiler
Ich habe mich geirrt, dies funktioniert gut mit Zeitstempeln, aber wenn Sie das Beispiel aus JavaDoc für org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME kopieren und einfügen, schlägt dies fehl. Das Beispiel, das sie bereitstellen, sollte X anstelle von Z für das Muster haben, da sie -05: 00 im Gegensatz zu -0500 enthalten.
MattWeiler
Ich habe diese Lösung ausprobiert und sie funktioniert, wenn Sie Datum oder Datum / Uhrzeit übergeben. Wenn die Werte jedoch leer sind, schlägt dies fehl.
Darshgohel
28

Ich habe hier eine Problemumgehung gefunden .

Spring / Spring Boot unterstützt nur das Datums- / Datums- / Uhrzeitformat in BODY-Parametern.

Die folgende Konfigurationsklasse unterstützt Datum / Uhrzeit in QUERY STRING (Anforderungsparameter):

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

beziehungsweise:

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

@DateTimeFormatDies funktioniert auch dann, wenn Sie mehrere Anforderungsparameter an eine Klasse binden ( in diesem Fall hilflose Anmerkungen):

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) {
// ...
Lu55
quelle
Wie kann man hier eine Konvertierungsausnahme abfangen?
Terraner
Dies ist die beste Antwort. Dies funktioniert auch dann, wenn das Datumsfeld ein verschachteltes Feld ist. Es ist auch besser, weil Sie auf diese Weise diese Konfiguration nur einmal hinzufügen müssen.
Bluelurker
18

Wie ich im Kommentar angegeben habe, können Sie diese Lösung auch in der Signaturmethode verwenden: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start

Anna
quelle
3

Ich bin auf dasselbe Problem gestoßen und habe hier meine Lösung gefunden (ohne Anmerkungen zu verwenden).

... müssen Sie mindestens eine Zeichenfolge ordnungsgemäß in [LocalDateTime] Converter in Ihrem Kontext registrieren, damit Spring sie jedes Mal automatisch für Sie ausführen kann, wenn Sie eine Zeichenfolge als Eingabe angeben und eine [LocalDateTime] erwarten. (Eine große Anzahl von Konvertern ist bereits von Spring implementiert und im Paket core.convert.support enthalten, aber keiner beinhaltet eine [LocalDateTime] -Konvertierung.)

In Ihrem Fall würden Sie also Folgendes tun:

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

und dann registrieren Sie einfach Ihre Bohne:

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

Mit Anmerkungen

Fügen Sie es Ihrem ConversionService hinzu:

@Component
public class SomeAmazingConversionService extends GenericConversionService {

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

}

und schließlich würden Sie dann @Autowire in Ihrem ConversionService:

@Autowired
private SomeAmazingConversionService someAmazingConversionService;

Weitere Informationen zu Conversions mit Spring (und Formatierung) finden Sie auf dieser Website . Seien Sie gewarnt, es gibt eine Menge Anzeigen, aber ich fand es definitiv eine nützliche Website und eine gute Einführung in das Thema.

Naruto Sempai
quelle
2

Folgendes funktioniert gut mit Spring Boot 2.1.6:

Regler

@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 Klassen:

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

    @DateTimeFormat(iso = DATE)
    LocalDate createdDate;
}

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

Testklasse:

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

}
Michael Hegner
quelle
2

SpringBoot 2.XX Update

Wenn Sie die Abhängigkeit ausnützt spring-boot-starter-webVersion 2.0.0.RELEASEoder höher, gibt es nicht mehr erforderlich , um explizit enthält jackson-datatype-jsr310Abhängigkeit, die bereits mit vorgesehen ist , spring-boot-starter-webdurch spring-boot-starter-json.

Dies wurde als Spring Boot-Problem Nr. 9297 behoben und die Antwort ist weiterhin gültig und relevant:

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

Die obigen Antworten haben bei mir nicht funktioniert, aber ich habe mich an eine gewandt, die hier funktioniert hat: https://blog.codecentric.de/de/2017/08/parsing-of-localdate-query-parameters-in-spring- boot / Das Gewinner-Snippet war die ControllerAdvice-Annotation, die den Vorteil hat, dass dieses Update auf alle Ihre Controller angewendet wird:

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

Sie können der Konfiguration hinzufügen, dass diese Lösung sowohl mit optionalen als auch mit nicht optionalen Parametern funktioniert.

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

Thien Ng.V
quelle
Hier sind einige Richtlinien für Wie schreibe ich eine gute Antwort? . Diese Antwort mag richtig sein, könnte aber von einer Erklärung profitieren. Nur-Code-Antworten gelten nicht als "gute" Antworten.
Trenton McKinney
0

Für die globale Konfiguration:

public class LocalDateTimePropertyEditor extends PropertyEditorSupport {

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

}

Und dann

@ControllerAdvice
public class InitBinderHandler {

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

}
hieund
quelle
Soll das LocalDateTimePropertyEditorsein OffsetDateTimePropertyEditoroder umgekehrt?
tom_mai78101