如何让Jakarta验证器忽略非CharSequence类型的Pattern注解验证异常?
问题描述
@jakarta.validation.constraints.Pattern注解仅适用于返回CharSequence类型的方法,但通过Gradle任务生成的Java文件中,所有getter方法都被添加了该注解,不管返回类型是什么。当validate标志为true时,调用validator.validate(view)会抛出jakarta.validation.UnexpectedTypeException异常。尝试用try-catch捕获无效,自定义Validator工厂也不知如何推进,需要解决如何让验证器忽略此类异常,或寻找替代方案。
相关代码示例
ExampleService.kt
import jakarta.validation.Validator import com.example.generated.DEXPDF // more imports @Service class ExampleService( // ... private val validator: Validator ) // ... some code private fun assertRequestValid(view: DEXPDF) { val violations = validator.validate(view) // throws UnexpectedTypeException if (violations.isNotEmpty()) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, violations.joinToString(", ")) } } // ... some more code
DEXPDF.java(生成文件片段)
// some code... @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v4.0.0", date = "2024-06-11T15:18:18+02:00") @Size(min = 1, max = 7) @JsonProperty("messageVersion") @Pattern(regexp = "F\\.[1-9][0-9]?\\.[1-9]?[0-9]") public String getMessageVersion() { return messageVersion; } /** * Sets the value of the messageVersion property. * * @param value * allowed object is * {@link String } * */ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v4.0.0", date = "2024-06-11T15:18:18+02:00") public void setMessageVersion(String value) { this.messageVersion = value; } /** * Gets the value of the messageSender property. * * @return * possible object is * {@link DEXPDF.MessageSender } * */ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v4.0.0", date = "2024-06-11T15:18:18+02:00") @Size(min = 1, max = 17) @JsonProperty("messageSender") @Pattern(regexp = "[A-Z]{2}[!-~]{1,15}") public DEXPDF.MessageSender getMessageSender() { return messageSender; } /** * Sets the value of the messageSender property. * * @param value * allowed object is * {@link DEXPDF.MessageSender } * */ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v4.0.0", date = "2024-06-11T15:18:18+02:00") public void setMessageSender(DEXPDF.MessageSender value) { this.messageSender = value; } /** * Gets the value of the messageRecipient property. * * @return * possible object is * {@link DEXPDF.MessageRecipient } * */ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v4.0.0", date = "2024-06-11T15:18:18+02:00") @JsonProperty("messageRecipient") @Pattern(regexp = "DE0[01][0-9]{4}") public DEXPDF.MessageRecipient getMessageRecipient() { // throws exception due to unexpected type return messageRecipient; } // more code...
解决方案
1. 自定义ConstraintValidatorFactory跳过无效Pattern验证
实现自定义的ConstraintValidatorFactory,替换默认的PatternValidator,让它在遇到非CharSequence类型的属性时直接跳过验证,避免抛出异常。
自定义验证器工厂及验证器
public class SkipInvalidPatternValidatorFactory implements ConstraintValidatorFactory { private final ConstraintValidatorFactory delegate; public SkipInvalidPatternValidatorFactory(ConstraintValidatorFactory delegate) { this.delegate = delegate; } @Override public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { // 针对PatternValidator做替换 if (PatternValidator.class.isAssignableFrom(key)) { return (T) new SkipInvalidPatternValidator(); } return delegate.getInstance(key); } @Override public void releaseInstance(ConstraintValidator<?, ?> instance) { delegate.releaseInstance(instance); } // 重写PatternValidator,跳过非CharSequence类型的验证 private static class SkipInvalidPatternValidator extends PatternValidator { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { if (!(value instanceof CharSequence)) { return true; // 非字符串类型直接视为有效,跳过验证 } return super.isValid(value, context); } } }
在Spring中配置自定义工厂
@Bean public Validator validator() { ValidatorFactory factory = Validation.byDefaultProvider() .configure() .constraintValidatorFactory(new SkipInvalidPatternValidatorFactory( Validation.byDefaultProvider().configure().getDefaultConstraintValidatorFactory() )) .buildValidatorFactory(); return factory.getValidator(); }
2. 预处理生成的Java文件,移除错误的Pattern注解
在Gradle生成JAXB代码后,添加一个任务扫描生成的Java文件,移除返回类型不是CharSequence或String的getter方法上的@Pattern注解。
Gradle任务示例
tasks.withType(GenerateJaxb) { doLast { fileTree(outputDir).include("**/*.java").each { file -> def content = file.text // 正则匹配并移除错误的@Pattern注解 def cleanedContent = content.replaceAll(/@Pattern\(regexp = "[^"]+"\)\s+(public (?!String|CharSequence)\w+ \w+\(\))/, '$1') file.text = cleanedContent } } }
3. 使用Validator代理类捕获并忽略异常
包装默认的Validator,在调用validate方法时捕获UnexpectedTypeException,并手动验证有效属性。
代理类示例
public class SafeValidator implements Validator { private final Validator delegate; public SafeValidator(Validator delegate) { this.delegate = delegate; } @Override public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) { try { return delegate.validate(object, groups); } catch (UnexpectedTypeException e) { Set<ConstraintViolation<T>> violations = new HashSet<>(); // 遍历对象的所有属性,只验证返回类型为CharSequence的属性 BeanDescriptor descriptor = delegate.getConstraintsForClass(object.getClass()); for (PropertyDescriptor propDesc : descriptor.getConstrainedProperties()) { if (CharSequence.class.isAssignableFrom(propDesc.getPropertyType())) { violations.addAll(delegate.validateProperty(object, propDesc.getPropertyName(), groups)); } } return violations; } } // 其他Validator方法直接委托给默认实现 @Override public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) { return delegate.validateProperty(object, propertyName, groups); } @Override public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) { return delegate.validateValue(beanType, propertyName, value, groups); } @Override public BeanDescriptor getConstraintsForClass(Class<?> clazz) { return delegate.getConstraintsForClass(clazz); } @Override public <T> T unwrap(Class<T> type) { return delegate.unwrap(type); } @Override public boolean isValid(Class<?> type) { return delegate.isValid(type); } }
在Spring中替换默认Validator
@Bean public Validator validator() { Validator defaultValidator = Validation.buildDefaultValidatorFactory().getValidator(); return new SafeValidator(defaultValidator); }
内容的提问来源于stack exchange,提问作者Omar El Hussein
相关产品推荐
相关产品推荐

