我在将 Spring 与 Jackson 集成时遇到问题。
我有一个 POJO,其中有一些带有自定义日期格式的
Instant
字段:
public class C{
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "UTC")
private Instant postedAtFrom;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "UTC")
private Instant postedAtTo;
}
然后我尝试使用这个 POJO 作为其余 GET 映射的查询参数:
@RestController
@RequestMapping("/c")
@RequiredArgsConstructor
@Slf4j
public class AnnounceController {
@Autowired
private final ObjectMapper objectMapper;
@PostConstruct
public void test() throws JsonProcessingException {
System.out.println(objectMapper.readValue("{\"postedAtFrom\":\"20-05-2022 15:50:07\"}", C.class));
}
@GetMapping
public BasicResponse<Page<Announce>> get(final C filter, @NonNull final Pageable page) {
return new BasicResponse<>(service.get(filter, page));
}
}
PostConstruct
代码工作正常,它打印的正是它应该打印的内容,但是每当我尝试使用相同的请求访问该端点时:
CURL
GET /c?postedAtFrom=20-05-2022 15:50:07 HTTP/1.1
Host: localhost
我得到这个例外:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'filter' on field 'postedAtFrom': rejected value [20-05-2022 15:50:07]
codes [typeMismatch.filter.postedAtFrom,typeMismatch.postedAtFrom,typeMismatch.java.time.Instant,typeMismatch]
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [filter.postedAtFrom,postedAtFrom]
arguments []
default message [postedAtFrom]]
default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.Instant' for property 'postedAtFrom'
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@com.fasterxml.jackson.annotation.JsonFormat java.time.Instant] for value [20-05-2022 15:50:07]
nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [20-05-2022 15:50:07]]
这是
ObjectMapper
bean 的代码:
@Bean
@Primary
public ObjectMapper configure(@NonNull final ObjectMapper objectMapper) {
final SimpleModule javaTimeModule = new JavaTimeModule()
.addDeserializer(Instant.class, InstantDeserializer.INSTANT)
.addSerializer(Instant.class, InstantSerializer.INSTANCE);
return objectMapper.registerModule(javaTimeModule);
}
我想使用 Spring 和 Jackson 设计的工具来解决这个问题。请不要建议“将其转换为字符串”或“发帖”等解决方法。
正如其他人在评论中所说,目前还不清楚您期望发生什么。我在下面做出假设。
考虑到
@JsonFormat
注释和自定义 ObjectMapper
,如果您期望 Jackson 在这里介入并解析 JSON,那是不会发生的。需要明确的是,这里的任何地方都不涉及 Jackson 或 JSON 解析。
您的
final C filter
处理程序方法参数未使用 @RequestBody
之类的内容进行注释(并且您没有在 GET Curl 调用中发送请求正文)。如果没有注释,Spring MVC 会将参数作为模型属性处理,并使用 ModelAttributeMethodProcessor
进行处理。
它会创建您类型的实例
C
并尝试使用查询(或表单 - 如果您使用过)参数填充其字段。在这种情况下,它将查找名为 postedAtFrom
的查询参数来匹配该字段(实际上是 bean/pojo 访问器,我也假设您有)。
处理器使用一组 Converter 实例将字符串值(查询参数)中的字段填充到模型属性类型可能使用的复杂类型中。
Spring Boot 维护自己的 default
Converter
实例列表。其中有一个用于 java.time
类型的 Jsr310DateTimeFormatAnnotationFormatterFactory
,它被设置为处理 String
到 Instant
的转换。
但是,您的查询参数值
20-05-2022 15:50:07
不会被解析为 Instant
,有关于 Instant
缺失的信息(时区)。在某些情况下,您可以注释您的字段@DateTimeFormat
以指定自定义格式,但我认为这在这里不起作用。
您可以将您的字段更改为
LocalDateTime
@DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
private LocalDateTime postedAtFrom;
使用与您的输入相匹配的适当模式。并且,每当您需要
Instant
时,请使用 LocalDateTime
的 toInstant
方法和适当的 ZoneOffset
。
或者,您可以使用自己的
Converter
实例列表来配置 Spring Boot,这些实例可以使用您自己的 Instant
直接解析为 DateTimeFormatter
。有关如何执行此操作的信息,请参阅这篇文章。