3.2.13.1. 定义约束

可以使用 javax.validation.constraints 包中的注解或者自定义注解来定义约束。可以在一个实体或 POJO 类声明、字段或 getter 方法以及中间件服务方法上设置注解。

在实体字段上使用标准验证注解的示例:

@Table(name = "DEMO_CUSTOMER")
@Entity(name = "demo_Customer")
public class Customer extends StandardEntity {

    @Size(min = 3) // length of value must be longer then 3 characters
    @Column(name = "NAME", nullable = false)
    protected String name;

    @Min(1) // minimum value
    @Max(5) // maximum value
    @Column(name = "GRADE", nullable = false)
    protected Integer grade;

    @Pattern(regexp = "\\S+@\\S+") // value must conform to the pattern
    @Column(name = "EMAIL")
    protected String email;

    //...
}

使用自定义类级别注解的示例(见下文):

@CheckTaskFeasibility(groups = {Default.class, UiCrossFieldChecks.class}) // custom validation annotation
@Table(name = "DEMO_TASK")
@Entity(name = "demo_Task")
public class Task extends StandardEntity {
    //...
}

验证服务方法的参数和返回值的示例

public interface TaskService {
    String NAME = "demo_TaskService";

    @Validated // indicates that the method should be validated
    @NotNull
    String completeTask(@Size(min = 5) String comment, @Valid @NotNull Task task);
}

如果需要方法参数的级联验证,可以使用 @Valid 注解,在上面的例子中,还将验证声明在 Task 对象上的约束。

约束组

约束组允许根据应用程序逻辑仅应用所有已定义约束的子集。例如,可能想强制用户输入实体属性的值,但是同时又能够通过某种内部机制设置此属性为空,为此,应该在约束注解上指定 groups 属性。然后,只有将相同的组传递给验证机制时,约束才会生效。

平台将以下约束组传递给验证机制:

  • RestApiChecks - 在 REST API 中验证时。

  • ServiceParametersChecks - 验证服务参数时。

  • ServiceResultChecks - 验证服务返回值时。

  • UiComponentChecks - 验证单个 UI 字段时。

  • UiCrossFieldChecks - 在实体编辑器提交时进行类级别约束验证时。

  • javax.validation.groups.Default - 除了 UI 编辑器上的提交操作之外,都会传递这个组。

验证消息

约束可包含要显示给用户的消息。

消息可以直接在验证注解上设置,例如:

@Pattern(regexp = "\\S+@\\S+", message = "Invalid format")
@Column(name = "EMAIL")
protected String email;

也可以将消息放在本地化消息包中并且使用以下格式在注解中指定消息: {msg://message_pack/message_key} 或简单的 {msg://message_key} (仅用于实体中)。例如:

@Pattern(regexp = "\\S+@\\S+", message = "{msg://com.company.demo.entity/Customer.email.validationMsg}")
@Column(name = "EMAIL")
protected String email;

或者,如果为实体定义约束并且消息在实体消息包中:

@Pattern(regexp = "\\S+@\\S+", message = "{msg://Customer.email.validationMsg}")
@Column(name = "EMAIL")
protected String email;

消息可以包含参数和表达式。参数包含在 {} 中,可使用的参数包括本地化消息或注解参数,例如 {min}{max}{value}。表达式包含在 ${} 中并且可以包含验证值变量( validatedValue ) 、注解参数(如 valuemin) 和 JSR-341 (EL 3.0)表达式。例如:

@Pattern(regexp = "\\S+@\\S+", message = "Invalid email: ${validatedValue}, pattern: {regexp}")
@Column(name = "EMAIL")
protected String email;

本地化消息值也可以包含参数和表达式。

自定义约束

可以使用编程或声明式验证来创建自己的特定领域约束。

要以编程方式的验证器创建约束,请执行以下操作:

  1. 在项目的 global 模块中创建注解。使用 @Constraint 进行标注。这个注解必须包含 messagegroupspayload 属性:

    @Target({ ElementType.TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = TaskFeasibilityValidator.class)
    public @interface CheckTaskFeasibility {
    
        String message() default "{msg://com.company.demo.entity/CheckTaskFeasibility.message}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
  2. 在项目的 global 模块中创建验证器类:

    public class TaskFeasibilityValidator implements ConstraintValidator<CheckTaskFeasibility, Task> {
    
        @Override
        public void initialize(CheckTaskFeasibility constraintAnnotation) {
        }
    
        @Override
        public boolean isValid(Task value, ConstraintValidatorContext context) {
            Date now = AppBeans.get(TimeSource.class).currentTimestamp();
            return !(value.getDueDate().before(DateUtils.addDays(now, 3)) && value.getProgress() < 90);
        }
    }
  3. 使用注解:

    @CheckTaskFeasibility(groups = UiCrossFieldChecks.class)
    @Table(name = "DEMO_TASK")
    @Entity(name = "demo_Task")
    public class Task extends StandardEntity {
    
        @Future
        @Temporal(TemporalType.DATE)
        @Column(name = "DUE_DATE")
        protected Date dueDate;
    
        @Min(0)
        @Max(100)
        @Column(name = "PROGRESS", nullable = false)
        protected Integer progress;
    
        //...
    }

还可以使用现有的约束的组合来创建自定义约束,例如:

@NotNull
@Size(min = 2, max = 14)
@Pattern(regexp = "\\d+")
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {})
public @interface ValidProductCode {
    String message() default "{msg://om.company.demo.entity/ValidProductCode.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

当使用复合约束时,生成的“约束违反”集合将包含每个约束的“约束违反”。如果想返回单个“约束违反”,请使用 @ReportAsSingleViolation 注解这个复合注解类。

CUBA 定义的验证注解

除了使用 javax.validation.constraints 包中的标准注解之外,可以使用在 CUBA 框架中定义的以下注解:

  • @RequiredView - 可以添加到服务方法定义中,以确保实体实例加载了视图中指定的所有属性。如果注解标记到方法上,则检查返回值。如果注解标记到参数上,则检查参数。如果返回值或者参数是集合,则检查集合中的所有元素。例如:

public interface MyService {
    String NAME = "sample_MyService";

    @Validated
    void processFoo(@RequiredView("foo-view") Foo foo);

    @Validated
    void processFooList(@RequiredView("foo-view") List<Foo> fooList);

    @Validated
    @RequiredView("bar-view")
    Bar loadBar(@RequiredView("foo-view") Foo foo);
}