Ik gebruik Spring Boot en heb jackson-datatype-jsr310
meegeleverd met Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.7.3</version>
</dependency>
Als ik een RequestParam probeer te gebruiken met een Java 8-datum/tijd-type,
@GetMapping("/test")
public Page<User> get(
@RequestParam(value = "start", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start) {
//...
}
en test het met deze URL:
/test?start=2016-10-8T00:00
Ik krijg de volgende foutmelding:
{
"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"
}
Antwoord 1, autoriteit 100%
TL;DR– je kunt het vastleggen als een tekenreeks met slechts @RequestParam
, of je kunt Spring de tekenreeks bovendien laten ontleden in een java-datum / tijdklasse via @DateTimeFormat
ook op de parameter.
de @RequestParam
is voldoende om de datum te pakken die u invoert na het =-teken, maar het komt in de methode als een String
. Daarom gooit het de cast-uitzondering.
Er zijn een paar manieren om dit te bereiken:
- ontleed de datum zelf en pak de waarde als een tekenreeks.
@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...
}
- Maak gebruik van de mogelijkheid van Spring om automatisch datumnotaties te ontleden en te verwachten:
@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).
}
Antwoord 2, autoriteit 90%
Je hebt alles goed gedaan 🙂 . Hieris een voorbeeld dat precies laat zien wat u zijn aan het doen. GewoonAnnoteer uw RequestParam met @DateTimeFormat
. Er is geen speciale GenericConversionService
of handmatige conversie in de controller nodig. Dezeblogpost schrijft erover.
@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
}
}
Ik denk dat je een probleem had met het formaat. Op mijn setup werkt alles goed.
Antwoord 3, autoriteit 41%
Ik heb hiereen tijdelijke oplossing gevonden.
Spring/Spring Boot ondersteunt alleen de datum/datum-tijd-notatie in BODY-parameters.
De volgende configuratieklasse voegt ondersteuning toe voor datum/datum-tijd in QUERY STRING (verzoekparameters):
// 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);
}
}
respectievelijk:
// 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);
}
}
Het werkt zelfs als je meerdere verzoekparameters aan een klasse bindt (@DateTimeFormat
annotatie hulpeloos in dit geval):
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) {
// ...
Antwoord 4, autoriteit 24%
Zoals ik in de opmerking plaatste, zou je deze oplossing ook kunnen gebruiken in de handtekeningmethode: @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start
Antwoord 5, autoriteit 11%
SpringBoot 2.X.X en nieuwer
Als je de afhankelijkheid gebruikt spring-boot-starter-web
versie 2.0.0.RELEASE
of hoger, het is niet langer nodig om expliciet jackson-datatype-jsr310
-afhankelijkheid, die al wordt geleverd met spring-boot-starter-web
via spring-boot-starter-json
.
Dit is opgelost als Spring Boot-probleem #9297en de antwoordis noggeldig en relevant:
<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
}
Antwoord 6, autoriteit 4%
Ik kwam hetzelfde probleem tegen en vond mijn oplossing hier(zonder annotaties te gebruiken)
…je moet op zijn minst een string correct registreren bij [LocalDateTime] Converter in
uw context, zodat Spring deze kan gebruiken om dit automatisch te doen voor
u elke keer dat u een String als invoer geeft en een [LocalDateTime] verwacht. (Een grote
aantal converters is al geïmplementeerd door Spring en bevat
in het pakket core.convert.support, maar geen enkele heeft betrekking op een [LocalDateTime]
conversie)
Dus in jouw geval zou je dit doen:
public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
public LocalDateTime convert(String source) {
DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
return LocalDateTime.parse(source, formatter);
}
}
en registreer dan gewoon je boon:
<bean class="com.mycompany.mypackage.StringToLocalDateTimeConverter"/>
Met annotaties
voeg het toe aan uw ConversionService:
@Component
public class SomeAmazingConversionService extends GenericConversionService {
public SomeAmazingConversionService() {
addConverter(new StringToLocalDateTimeConverter());
}
}
en tot slot zou u dan @Autowire in uw ConversionService:
@Autowired
private SomeAmazingConversionService someAmazingConversionService;
Je kunt meer lezen over conversies met spring (en opmaak) op deze website. Wees gewaarschuwd, het heeft een heleboel advertenties, maar ik vond het zeker een nuttige site en een goede introductie tot het onderwerp.
Antwoord 7, autoriteit 3%
Het volgende werkt goed met Spring Boot 2.1.6:
Controller
@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-lessen:
@Value
public class RequestParameter {
@DateTimeFormat(iso = DATE_TIME)
LocalDateTime created;
@DateTimeFormat(iso = DATE)
LocalDate createdDate;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostBody {
LocalDate date;
}
Testles:
@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));
}
}
Antwoord 8
De bovenstaande antwoorden werkten niet voor mij, maar ik blunderde naar een die hier wel werkte: https://blog.codecentric.de/en/2017/08/parsing-of-localdate-query-parameters-in-spring-boot/ Het winnende fragment was de ControllerAdvice-annotatie, die het voordeel heeft dat deze oplossing op al je controllers wordt toegepast:
@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 );
}
} );
}
}
Antwoord 9
Je kunt toevoegen aan config, deze oplossing werkt zowel met optionele als met niet-optionele parameters.
@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);
}
};
}
Antwoord 10
Hier is nog een algemene oplossing met parameterconverter:
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)));
}
}
Antwoord 11
Voor globale configuratie:
public class LocalDateTimePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalDateTime.parse(text, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
}
En dan
@ControllerAdvice
public class InitBinderHandler {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(OffsetDateTime.class, new OffsetDateTimePropertyEditor());
}
}
Antwoord 12
U kunt de datum/tijd-indeling globaal configureren in application properties
.
Vind ik leuk:
spring.mvc.format.date=jjjj-MM-dd
spring.mvc.format.date-time=jjjj-MM-dd UU:mm:ss
spring.mvc.format.time=UU:mm:ss
Check in mavern: org.springframework.boot:spring-boot-autoconfigure:2.5.3
Antwoord 13
Ik had een soortgelijk probleem in een gerelateerde context
Ik gebruik WebRequestDataBinder om de verzoekparameters dynamisch aan een model toe te wijzen.
Object domainObject = ModelManager.getEntity(entityName).newInstance();
WebRequestDataBinder binder = new WebRequestDataBinder(domainObject);
binder.bind(request);
Dit stuk code werkt voor primitieven, maar werkte niet voor kenmerken van het type LocalDateTime
Om het probleem op te lossen, heb ik, voordat ik binder.bind aanroep, een aangepaste editor geregistreerd voordat ik bind() aanroep
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());
}
}
);
Dit loste het probleem op.