Constraint
Constraint基本定义
- 标注@Constraint 注解,同时指定对应的ConstraintValidator,可以配置多个,但是每个ConstraintValidator需要各自处理一种类型,不能有交集
- 定义基础message属性: String message() default "{com.acme.constraint.MyConstraint.message}";
- 定义基础group属性: Class<?>[] groups() default { };
- 定义基础payload属性(额外元信息): Class<? extends Payload>[] payload() default { };
- 定义可选validationAppliesTo属性(泛型、数组类型需要): ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;设置不正确会ConstraintDeclarationException
@Documented
@Constraint(validatedBy = OrderNumberValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface OrderNumber {
String message() default "{com.acme.constraint.OrderNumber.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
同一个类型使用多个相同的Constraint
- 定义一个List注解,有个value属性返回 自定义Constraint列表
- 自定义Constraint需要标注@Repeatable(List.class)
/**
* Validate a zip code for a given country
* The only supported type is String
*/
@Documented
@Constraint(validatedBy = ZipCodeValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface ZipCode {
String countryCode();
String message() default "{com.acme.constraint.ZipCode.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Defines several @ZipCode annotations on the same element
* @see (@link ZipCode}
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
ZipCode[] value();
}
}
public class Address {
@ZipCode(countryCode = "fr", groups = Default.class, message = "zip code is not valid")
@ZipCode(
countryCode = "fr",
groups = SuperUser.class,
message = "zip code invalid. Requires overriding before saving."
)
private String zipCode;
}
public class Address {
@ZipCode.List( {
@ZipCode(countryCode="fr", groups=Default.class,
message = "zip code is not valid"),
@ZipCode(countryCode="fr", groups=SuperUser.class,
message = "zip code invalid. Requires overriding before saving.")
} )
private String zipCode;
}
组合Constraint
@Pattern(regexp = "[0-9]*")
@Size
@Constraint(validatedBy = FrenchZipCodeValidator.class)
@Documented
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface FrenchZipCode {
String message() default "Wrong zip code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@OverridesAttribute(constraint = Size.class, name = "min")
@OverridesAttribute(constraint = Size.class, name = "max")
int size() default 5;
@OverridesAttribute(constraint = Size.class, name = "message")
String sizeMessage() default "{com.acme.constraint.FrenchZipCode.zipCode.size}";
@OverridesAttribute(constraint = Pattern.class, name = "message")
String numberMessage() default "{com.acme.constraint.FrenchZipCode.number.size}";
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
FrenchZipCode[] value();
}
}
自定义注解的group、payload、validationAppliesTo属性会被忽略
自定义Validator
基本概念
实现ConstraintValidator接口,实现isValid方法(会并发调用isValid,需要保证线程安全)
// A 自定义的注解,T 需要校验的类型
public interface ConstraintValidator<A extends Annotation, T> {
// 校验前的准备工作
default void initialize(A constraintAnnotation) {
}
boolean isValid(T value, ConstraintValidatorContext context);
}
注意:
- T 没有泛型参数,或者泛型参数是具体的类型而不是 <?> 这种
- Validator 可以校验参数列表、跨参数校验,但是需要自定义Validator标注SupportedValidationTarget注解指定
@Documented
@Target({ TYPE })
@Retention(RUNTIME)
public @interface SupportedValidationTarget {
ValidationTarget[] value();
}
public enum ValidationTarget {
// 返回值
ANNOTATED_ELEMENT,
// 入参、构造器、跨参数
PARAMETERS
}
- Validator可以同时处理单个标注的类型以及参数列表(跨参数)。如果处理多个参数需要使用Object[] 正确的定义:
//String is not making use of generics
public class SizeValidatorForString implements ConstraintValidator<Size, String> {
}
//Collection uses generics but the raw type is used
public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection> {
}
//Collection uses generics and unbounded wildcard type
public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection<?>> {
}
//Validator for cross-parameter constraint
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class DateParametersConsistentValidator
implements ConstraintValidator<DateParametersConsistent, Object[]> {
}
//Validator for both annotated elements and executable parameters
@SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT, ValidationTarget.PARAMETERS})
public class ELScriptValidator implements ConstraintValidator<ELScript, Object> {
}
错误的定义:
//parameterized type
public class SizeValidatorForString implements ConstraintValidator<Size, Collection<String>> {
}
//parameterized type using bounded wildcard
public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection<? extends Address>> {
}
//cross-parameter validator accepting the wrong type
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class NumberPositiveValidator implements ConstraintValidator<NumberPositive, Number> {
}
- Validator可以被缓存,通过ConstraintValidatorFactory获取实例
- initialize只会调用一次,isValid每次校验都会调用
- 在initialize、isValid产生的异常会以ValidationException抛出
- 在isValid里不能更改传入的值
ConstraintValidatorContext
ConstraintValidatorContext提供了验证时需要的上下文。 当验证到无效时将产生一个ConstraintViolation对象,可以用于展示异常的信息。 如果验证失败了,返回抛出ValidationException。
public interface ConstraintValidatorContext {
/**
* 禁用默认的异常message模板
*/
void disableDefaultConstraintViolation();
/**
* 获取默认未填充的message模板
*/
String getDefaultConstraintMessageTemplate();
/**
* 获取当前的时间。为Past、Future constraint作为参考
*/
ClockProvider getClockProvider();
/**
* 返回一个builder用于构建错误的信息
* <pre >
* context.buildConstraintViolationWithTemplate( "this detail is wrong" )
* .addConstraintViolation();
* </pre>
* 在构建错误信息时,必须先调用builder的addConstraintViolation,否则抛出IllegalStateException
* @param messageTemplate 自定义未填充消息模版
* @return builder
*/
ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);
/**
* 返回provider允许访问的实例
* @param type 实例类型
* @return 实例
* @param <T> 类型
*/
<T> T unwrap(Class<T> type);
interface ConstraintViolationBuilder {
/**
* 构建一个节点到validate关联的path上
* @param name 节点名称,不能为“.”
* @return node
*/
NodeBuilderDefinedContext addNode(String name);
/**
* 构建一个属性节点到validate关联的path上
* @param name 节点名称
* @return node
*/
NodeBuilderCustomizableContext addPropertyNode(String name);
/**
* 添加一个类级别的bean 节点到path上。bean node 为叶子结点
* @return bean node
*/
LeafNodeBuilderCustomizableContext addBeanNode();
/**
* 添加一个元素节点到path
* @param name 名称
* @param containerType 类型
* @param typeArgumentIndex 类型参数索引
* @return element node
*/
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name,
Class<?> containerType, Integer typeArgumentIndex);
/**
* 添加一个方法参数到path上,一般用在跨参数constraint
* @param index 参数所有
* @return parameter node
*/
NodeBuilderDefinedContext addParameterNode(int index);
/**
* 用于创建一个新的ConstraintViolation
* @return ConstraintViolation
*/
ConstraintValidatorContext addConstraintViolation();
}
}
Example
简单(验证字符串是否以xxx开头)
通过使用initialize预先获取规则,然后实时调用isValid进行校验
/**
* Check that a String begins with one of the given prefixes.
*/
public class BeginsWithValidator implements ConstraintValidator<BeginsWith, String> {
private Set<String> allowedPrefixes;
/**
* Configure the constraint validator based on the elements specified at the time it was
* defined.
*
* @param constraint the constraint definition
*/
@Override
public void initialize(BeginsWith constraint) {
allowedPrefixes = Arrays.stream( constraint.value() )
.collect( collectingAndThen( toSet(), Collections::unmodifiableSet ) );
}
/**
* Validate a specified value. returns false if the specified value does not conform to
* the definition.
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null )
return true;
return allowedPrefixes.stream()
.anyMatch( value::startsWith );
}
}
跨参数验(直接验证方法的多个参数)
通过SupportedValidationTarget指定待验证的目标为方法参数,同时值的类型使用Object[]来接收多个不同类型的参数。 在isValid进行验证
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class DateParametersConsistentValidator implements
ConstraintValidator<DateParametersConsistent, Object[]> {
/**
* Validate a specified value. returns false if the specified value does not conform to
* the definition
*/
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
if ( value.length != 3 ) {
throw new IllegalArgumentException( "Unexpected method signature" );
}
// one or both limits are unbounded => always consistent
if ( value[1] == null || value[2] == null ) {
return true;
}
return ( (Date) value[1] ).before( (Date) value[2] );
}
}
泛型+跨参数
通过SupportedValidationTarget指定待验证的目标为泛型、参数。 使用Object来接收参数。
@SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT, ValidationTarget.PARAMETERS})
public class ELScriptValidator implements ConstraintValidator<ELScript, Object> {
public void initialize(ELScript constraint) {
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
}
}
使用ConstraintValidatorContext
- 在initialize里获取注解的配置
- 在isValid验证的时候首先禁用默认的异常信息,然后在验证失败的时候构建新的错误信息 buildConstraintViolationWithTemplate + addConstraintViolation
- 返回false,抛出ValidateException并返回异常信息
public class SerialNumberValidator implements ConstraintValidator<SerialNumber, String> {
private int length;
/**
* Configure the constraint validator based on the elements specified at the time it was
* defined.
*
* @param constraint the constraint definition
*/
@Override
public void initialize(SerialNumber constraint) {
this.length = constraint.length();
}
/**
* Validate a specified value. returns false if the specified value does not conform to
* the definition.
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null )
return true;
context.disableDefaultConstraintViolation();
if ( !value.startsWith( "SN-" ) ) {
String wrongPrefix = "{com.acme.constraint.SerialNumber.wrongprefix}";
context.buildConstraintViolationWithTemplate( wrongPrefix )
.addConstraintViolation();
return false;
}
if ( value.length() != length ) {
String wrongLength = "{com.acme.constraint.SerialNumber.wronglength}";
context.buildConstraintViolationWithTemplate( wrongLength )
.addConstraintViolation();
return false;
}
return true;
}
}
基于ClockProvider验证时间
通过ClockProvider获取当前时间进行验证 传入的参数
public class PastValidatorForZonedDateTime implements ConstraintValidator<Past, ZonedDateTime> {
@Override
public boolean isValid(ZonedDateTime value, ConstraintValidatorContext context) {
if ( value == null ) {
return true;
}
ZonedDateTime now = ZonedDateTime.now( context.getClockProvider().getClock() );
return value.isBefore( now );
}
}
ConstraintValidatorFactory
用于创建ConstraintValidator。 所有被获取的ConstraintValidator都必须被release掉。 ConstraintValidatorFactory中不能缓存实例。
public interface ConstraintValidatorFactory {
/**
* @param key The class of the constraint validator to instantiate
* @param <T> The type of the constraint validator to instantiate
*
* @return A new constraint validator instance of the specified class
*/
<T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
/**
* Signals {@code ConstraintValidatorFactory} that the instance is no longer
* being used by the Jakarta Bean Validation provider.
*
* @param instance validator being released
*
* @since 1.1
*/
void releaseInstance(ConstraintValidator<?, ?> instance);
}