REST接口使用LocalDate接收参数遇400错误,如何完善日期校验逻辑?
问题根因
你遇到的400错误发生在Jackson反序列化请求体的阶段,早于Bean Validation的校验流程,所以你定义的@NotNull注解还没生效,请求就被Spring MVC默认逻辑拦截返回了固定格式的400响应。具体两类非法输入的触发逻辑:
- 传入空字符串、不符合
yyyy-MM-dd格式的日期值、2月30日这类逻辑上不存在的日期时,Jackson内置的LocalDate反序列化器会直接抛出DateTimeParseException,被包装为HttpMessageNotReadableException后直接返回400 - 如果传入JSON null值正常会走到校验逻辑触发
@NotNull报错,如果你连这个场景也返回默认400,说明你没有配置全局异常处理器接管校验抛出的异常
解决方案
步骤1:自定义LocalDate反序列化器
统一处理空字符串、格式错误、逻辑无效日期的场景,你可以选择反序列化失败时返回null走后续校验,或者直接抛出带自定义错误信息的异常:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; public class CustomLocalDateDeserializer extends StdDeserializer<LocalDate> { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; public CustomLocalDateDeserializer() { super(LocalDate.class); } @Override public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException { String dateStr = parser.getText().trim(); // 空字符串直接返回null,后续触发@NotNull校验 if (dateStr.isBlank()) { return null; } try { return LocalDate.parse(dateStr, DATE_FORMATTER); } catch (DateTimeParseException e) { // 格式错误/无效日期抛自定义异常,后续全局异常处理器捕获 throw new IllegalArgumentException("日期格式非法,必须为yyyy-MM-dd格式的有效日期"); } } }
注册反序列化器
你可以选择单个字段标注生效,或者全局配置所有LocalDate字段生效:
- 单个字段生效:给PSRequest的字段加
@JsonDeserialize注解
public class PSRequest { @JsonDeserialize(using = CustomLocalDateDeserializer.class) @NotNull(message = "fromDate cannot be null") protected LocalDate fromDate; @JsonDeserialize(using = CustomLocalDateDeserializer.class) @NotNull(message = "toDate cannot be null") protected LocalDate toDate; }
- 全局生效:配置Jackson自定义ObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JacksonConfig { @Bean public ObjectMapper customObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); // 注册自定义LocalDate反序列化器 javaTimeModule.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer()); objectMapper.registerModule(javaTimeModule); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return objectMapper; } }
步骤2:配置全局异常处理器
接管反序列化、校验两个阶段的所有异常,返回自定义结构的错误响应:
import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.http.converter.HttpMessageNotReadableException; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { // 处理JSR校验触发的参数错误 @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationError(MethodArgumentNotValidException e) { Map<String, String> errorMap = new HashMap<>(); e.getBindingResult().getAllErrors().forEach(error -> { String field = ((FieldError) error).getField(); String message = error.getDefaultMessage(); errorMap.put(field, message); }); return ResponseEntity.badRequest().body(errorMap); } // 处理Jackson反序列化触发的参数错误 @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity<Map<String, String>> handleDeserializationError(HttpMessageNotReadableException e) { Map<String, String> errorMap = new HashMap<>(); Throwable rootCause = e.getRootCause(); if (rootCause instanceof IllegalArgumentException) { errorMap.put("error", rootCause.getMessage()); } else { errorMap.put("error", "请求参数格式非法"); } return ResponseEntity.badRequest().body(errorMap); } // 处理单个参数校验触发的约束错误 @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<Map<String, String>> handleConstraintError(ConstraintViolationException e) { Map<String, String> errorMap = new HashMap<>(); for (ConstraintViolation<?> violation : e.getConstraintViolations()) { errorMap.put(violation.getPropertyPath().toString(), violation.getMessage()); } return ResponseEntity.badRequest().body(errorMap); } }
配置完成后,你提到的三类非法输入都会返回你自定义的错误信息,不会再出现Spring默认的400响应结构。
内容的提问来源于stack exchange,提问作者Ankit Malhotra
相关产品推荐
相关产品推荐

