You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 10:55:54