Spring Boot中如何为@RequestParam参数使用@JsonDeserialize
@RequestParam绑定LocalDateTime时自定义Jackson反序列化器无效的解决方法
问题场景
我尝试用Jackson自定义反序列化器处理LocalDateTime类型的@RequestParam参数,发现deserialize方法从未被调用,完全不生效。但使用@RequestBody接收封装类中的LocalDateTime参数时,该反序列化器能正常工作。
当前相关代码
@RequestParam方法(无效)
@GetMapping("/search") public String search( @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class) @RequestParam(value = "startTime", required = false) LocalDateTime startTime, @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class) @RequestParam(value = "endTime", required = false) LocalDateTime endTime )
@RequestBody方法(正常生效)
@PostMapping("/test") public LocalDateTime getBillingListByDate(@RequestBody SearchQuery searchQuery) { return searchQuery.getStartTime(); }
SearchQuery类
public class SearchQuery { @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class) private LocalDateTime startTime; @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class) private LocalDateTime endTime; // getter and setter }
自定义Jackson反序列化器
public class CustomLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> { private final DateTimeFormatter FORMATTER1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final DateTimeFormatter FORMATTER2 = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public CustomLocalDateTimeDeserializer() { this(LocalDateTime.class); } CustomLocalDateTimeDeserializer(Class<LocalDateTime> deserializer) { super(deserializer); } @Override public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String text = jsonParser.getText().trim(); if (ObjectUtils.isEmpty(text)) { return null; } LocalDateTime localDateTime = null; try { localDateTime = LocalDateTime.parse(text, FORMATTER1); } catch (Exception e) { try { LocalDate parse = LocalDate.parse(text, FORMATTER2); LocalTime time = LocalTime.of(0, 0, 0); localDateTime = LocalDateTime.of(parse, time); } catch (Exception ignore) { } } return localDateTime; } }
当前配置类
@Configuration public class GlobalDateConfig { private final String formatter = "yyyy-MM-dd HH:mm:ss"; @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> { DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern(formatter); builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter)); builder.deserializerByType(LocalDateTime.class, new CustomLocalDateTimeDeserializer()); }; } }
依赖版本
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.11.2</version> </dependency>
问题原因
Jackson的@JsonDeserialize注解仅作用于JSON请求体的反序列化流程,也就是@RequestBody对应的处理逻辑。而@RequestParam的参数是从URL查询参数、表单参数中获取的字符串,Spring会通过自身的Converter<String, T>机制完成字符串到目标类型的转换,不会触发Jackson的反序列化器,因此直接在@RequestParam参数上标注@JsonDeserialize完全无效。
解决方案
方案1:全局注册自定义Converter
创建实现Converter<String, LocalDateTime>的类,迁移原反序列化器的逻辑,再注册到Spring转换服务中。
自定义Converter类
import org.springframework.core.convert.converter.Converter; import org.springframework.util.ObjectUtils; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class CustomLocalDateTimeConverter implements Converter<String, LocalDateTime> { private final DateTimeFormatter FORMATTER1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final DateTimeFormatter FORMATTER2 = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Override public LocalDateTime convert(String source) { String text = source.trim(); if (ObjectUtils.isEmpty(text)) { return null; } LocalDateTime localDateTime = null; try { localDateTime = LocalDateTime.parse(text, FORMATTER1); } catch (Exception e) { try { LocalDate parse = LocalDate.parse(text, FORMATTER2); LocalTime time = LocalTime.of(0, 0, 0); localDateTime = LocalDateTime.of(parse, time); } catch (Exception ignore) { } } return localDateTime; } }
修改配置类注册Converter
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class GlobalDateConfig implements WebMvcConfigurer { private final String formatter = "yyyy-MM-dd HH:mm:ss"; @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> { DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern(formatter); builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter)); builder.deserializerByType(LocalDateTime.class, new CustomLocalDateTimeDeserializer()); }; } // 注册自定义Converter @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new CustomLocalDateTimeConverter()); } }
方案2:使用@InitBinder全局绑定
通过@ControllerAdvice配合@InitBinder全局配置LocalDateTime的绑定规则。
全局绑定配置类
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; import java.beans.PropertyEditorSupport; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @ControllerAdvice public class GlobalDateTimeBinder { private final DateTimeFormatter FORMATTER1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final DateTimeFormatter FORMATTER2 = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { if (text == null || text.trim().isEmpty()) { setValue(null); return; } LocalDateTime localDateTime = null; try { localDateTime = LocalDateTime.parse(text.trim(), FORMATTER1); } catch (Exception e) { try { LocalDate parse = LocalDate.parse(text.trim(), FORMATTER2); LocalTime time = LocalTime.of(0, 0, 0); localDateTime = LocalDateTime.of(parse, time); } catch (Exception ignore) { } } setValue(localDateTime); } }); } }
注意事项
两种方案二选一即可,无需同时配置。配置完成后,@RequestParam参数上的@JsonDeserialize注解可以移除。
内容的提问来源于stack exchange,提问作者wang junweiiii
相关产品推荐
相关产品推荐

