Spring Boot中Jakarta枚举自定义验证失效,需按自定义规则抛错
问题:自定义枚举验证器未触发,反序列化先抛出异常
问题场景
自定义了枚举验证器和注解,用于限制DTO中枚举字段的可选值,但传入非法字符串时,先触发Jackson反序列化异常,自定义验证逻辑完全没执行。
自定义验证器代码
package my.dtos.enums; import my.dtos.CourseStatus; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.Arrays; public class CourseStatusValidator implements ConstraintValidator<CourseStatusType, CourseStatus> { private CourseStatus[] subset; @Override public void initialize(CourseStatusType constraint) { this.subset = constraint.anyOf(); } @Override public boolean isValid(CourseStatus value, ConstraintValidatorContext context) { return value == null || Arrays.asList(subset).contains(value); } }
注解定义代码
package my.dtos.enums; import my.dtos.CourseStatus; import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = CourseStatusValidator.class) public @interface CourseStatusType { CourseStatus[] anyOf(); String message() default "must be any of {anyOf}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
DTO使用代码
@CourseStatusType(anyOf = {CourseStatus.PENDING, CourseStatus.CANCELED}) private CourseStatus status;
控制器代码
@PutMapping("/{id}") public Course update(@PathVariable("id") Long id, @RequestBody @Validated CourseRequest request)
错误信息
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type com.some.path.CourseStatus from String "WRONG_DATA_PASSWED": not one of the values accepted for Enum class: [CANCELED, DONE, IN_PROGRESS, PENDING]
原因分析
Spring MVC的执行顺序是JSON反序列化(Jackson)优先于Bean Validation(@Validated)。当传入的字符串不属于CourseStatus枚举的合法值时,Jackson会直接抛出反序列化异常,请求根本到不了自定义验证器执行的阶段。
解决方案
通过自定义Jackson反序列化器,对非法枚举值做容错处理,让反序列化流程正常完成,之后触发自定义验证逻辑。
步骤1:编写容错枚举反序列化器
package my.dtos.enums; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import my.dtos.CourseStatus; import java.io.IOException; public class TolerantCourseStatusDeserializer extends JsonDeserializer<CourseStatus> { @Override public CourseStatus deserialize(JsonParser parser, DeserializationContext context) throws IOException { String inputValue = parser.getText().trim(); // 匹配合法枚举值 for (CourseStatus status : CourseStatus.values()) { if (status.name().equals(inputValue)) { return status; } } // 非法值返回null,交给验证器处理 return null; } }
步骤2:在DTO字段上绑定反序列化器
修改DTO中的字段定义,添加@JsonDeserialize注解指定自定义反序列化器:
@CourseStatusType(anyOf = {CourseStatus.PENDING, CourseStatus.CANCELED}) @JsonDeserialize(using = TolerantCourseStatusDeserializer.class) private CourseStatus status;
可选优化:调整验证错误提示
修改CourseStatusType注解的默认提示信息,让用户更清晰:
String message() default "课程状态必须是{PENDING, CANCELED}中的有效值";
替代方案:映射非法值到特定枚举
如果不想返回null,可以在CourseStatus枚举中新增一个INVALID枚举项,反序列化时将非法值映射为该枚举:
// 枚举类新增项 public enum CourseStatus { PENDING, CANCELED, DONE, IN_PROGRESS, INVALID } // 反序列化器修改返回值 return CourseStatus.INVALID;
此时验证器会检测到INVALID不在允许的子集里,触发自定义错误提示。
效果验证
完成上述配置后,传入非法字符串时,Jackson会将其转为null或INVALID,反序列化流程正常完成,随后自定义验证器执行,检查值是否在指定子集内,最终抛出自定义的验证错误信息,而非Jackson的反序列化异常。
内容的提问来源于stack exchange,提问作者PHP Avenger
相关产品推荐
相关产品推荐

