Jsr深度探险之限制声明 + 验证流程

Constraint可以应用在带泛型的容器:Map、List、Optional,以及不带泛型的OptionalInt。

验证生效的要求

  1. 验证注解必须被注解在属性、方法、字段、getter(getXX,isXX)方法上
  2. static字段、方法无法验证
  3. 可以应用在接口、父类上
  4. 验证的目标:type、field(字段)、properties(get方法)、method-parameter、method-return-value、constructor-parameter、constructor-return-value、cross-parameter、container
  5. 不要重复验证field、properties
  6. 对象遍历、级联验证使用@Valid
  7. List、Map、Set、Collection、array、iterator 等容器,都可以使用@Valid注解实现验证 示例:
 

java

代码解读

复制代码

public class User { // preferred style as of Jakarta Bean Validation 2.0 private List<@Valid PhoneNumber> phoneNumbers; // traditional style; continues to be supported @Valid private List<PhoneNumber> phoneNumbers; /** * 会重复验证 */ @Valid private List<@Valid PhoneNumber> phoneNumbers; } public class User { // preferred style as of Jakarta Bean Validation 2.0 private Map<AddressType, @Valid Address> addressesByType; // traditional style; continues to be supported @Valid private Map<AddressType, Address> addressesByType; /** * 会重复验证 */ @Valid private Map<AddressType, @Valid Address> addressesByType; } public class User { private Map<@Valid AddressType, @Valid Address> addressesByType; } public class User { private Map<String, List<@Valid Address>> addressesByType; } public class User { private Map<String, Map<@Valid AddressType, @Valid Address>> addressesByUserAndType; }

Constraint声明

Constraint的声明通过注解标注到类、接口上实现。 当Constraint标注到类上,实例会被传入到定义的ConstraintValidator进行验证。 当Constraint标注到字段上,字段的值会被传入到ConstraintValidator进行验证。 当Constraint标注到getter方法上,方法调用后的返回值会被传入到ConstraintValidator进行验证。

Group

基本

Group 是constraint的一个子集,可以只验证某个子集。 每个Constraint需要声明所在的分组,默认在Default。 Group的表现形式为定义一个interface。如下:

 

java

代码解读

复制代码

/** * Validation group verifing that a user is billable */ public interface Billable {} /** * Customer can buy without any harrassing checking process */ public interface BuyInOneClick { }

一个Constraint可以定义多个Group,如下:

 

java

代码解读

复制代码

/** * User representation */ public class User { @NotNull private String firstname; @NotNull(groups = Default.class) private String lastname; @NotNull(groups = {Billable.class, BuyInOneClick.class}) private CreditCard defaultCreditCard; }

继承

Group可以继承。

 

java

代码解读

复制代码

/** * Customer can buy without harrassing checking process */ public interface BuyInOneClick extends Default, Billable {}

Group 顺序

默认情况下,在执行校验的时候多个Group是没有特定顺序的。 Group不能形成循环依赖,否则会抛出异常GroupDefinitionException 有些情况下,需要指定Group的执行顺序。 在需要验证Bean中通过使用GroupSequence指定Group的顺序。

 

java

代码解读

复制代码

@ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class) public class Address { @NotNull @Size(max = 50) private String street1; @NotNull @ZipCode private String zipCode; @NotNull @Size(max = 30) private String city; /** * check coherence on the overall object * Needs basic checking to be green first */ public interface HighLevelCoherence {} /** * check both basic constraints and high level ones. * high level constraints are not checked if basic constraints fail */ @GroupSequence({Default.class, HighLevelCoherence.class}) public interface Complete {} }

使用Address.Complete标注Group顺序,在验证的时候先验证Default,再验证HighLevelCoherence。如果Default失败了不会再验证HighLevelCoherence。

重定义Default分组

有些时候需要修改默认分组来替代Default。 通过在待校验类上标注@GroupSequence同时指定当前类、对应的分组类实现自定义默认分组

 

java

代码解读

复制代码

@GroupSequence({Address.class, HighLevelCoherence.class}) @ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class) public class Address { @NotNull @Size(max = 50) private String street1; @NotNull @ZipCode private String zipCode; @NotNull @Size(max = 30) private String city; /** * check coherence on the overall object * Needs basic checking to be green first */ public interface HighLevelCoherence {} }

隐式分组

默认情况下,如果Constraint没有显式指定Group,那么默认使用Default分组。 如果有父类指定了Constraint,那么子类默认使用父类的分组。

 

java

代码解读

复制代码

/** * Auditable object contract */ public interface Auditable { @NotNull String getCreationDate(); @NotNull String getLastUpdate(); @NotNull String getLastModifier(); @NotNull String getLastReader(); } /** * Represents an order in the system */ public class Order implements Auditable { private String creationDate; private String lastUpdate; private String lastModifier; private String lastReader; private String orderNumber; public String getCreationDate() { return this.creationDate; } public String getLastUpdate() { return this.lastUpdate; } public String getLastModifier() { return this.lastModifier; } public String getLastReader() { return this.lastReader; } @NotNull @Size(min=10, max=10) public String getOrderNumber() { return this.orderNumber; } }

Group转换

当在使用@Valid级联校验时,可能需要使用不同的分组,而不是请求时原始的分组。可以通过使用@ConvertGroup进行声明

 

java

代码解读

复制代码

package jakarta.validation.groups; /** * Converts group {@code from} to group {@code to} during cascading. * <p> * Can be used everywhere {@link Valid} is used and must be on an element * annotated with {@code Valid}. * * @author Emmanuel Bernard * @since 1.1 */ @Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Repeatable(List.class) @Documented public @interface ConvertGroup { /** * The source group of this conversion. * @return the source group of this conversion */ Class<?> from() default Default.class; /** * The target group of this conversion. * @return the target group of this conversion */ Class<?> to(); /** * Defines several {@link ConvertGroup} annotations * on the same element. */ @Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented public @interface List { ConvertGroup[] value(); } }

@ConvertGroup和@ConvertGroup.List可以使用在任何使用了@Valid的地方(关联、方法、构造函数请求参数、返回值)。如果没有@Valid会抛出ConstraintDeclarationException异常。 当标注了@Valid后,验证会进行传播,级联使用对应的Group进行验证,直到遇到ConvertGroup。 如果当前Group是ConvertGroup配置的from,那么会此时使用ConvertGroup的To对应的Group进行处理。 如果ConvertGroup为指定,默认为Default Group。 当ConvertGroup匹配了后就不会继续往下匹配了。比如:1: from:A to:B 2. from:B to:C 。此时验证时,如果当前Group是A,那么匹配到了B后,就不会再次匹配了。 如果标注的多个ConvertGroup中,from相同,那么会抛出ConstraintDeclarationException异常。 ConvertGroup的from不能为group sequence,to可以。

以下示例会转换User当前的Group到Address需要的Group。 当验证User实例时,如果是Default,那么对应级联的内部的验证就会使用BasicPostal group,如果是Complete,那么会是FullPostal。

 

java

代码解读

复制代码

public interface Complete extends Default {} public interface BasicPostal {} public interface FullPostal extends BasicPostal {} public class Address { @NotNull(groups=BasicPostal.class) String street1; String street2; @ZipCode(groups=BasicPostal.class) String zipCode; @CodeChecker(groups=FullPostal.class) String doorCode; } public class User { @Valid @ConvertGroup(from=Default.class, to=BasicPostal.class) @ConvertGroup(from=Complete.class, to=FullPostal.class) Set<Address> getAddresses() { [...] } }

Group 转换也可以用于在容器的元素

 

java

代码解读

复制代码

public class User { Set< @Valid @ConvertGroup(from=Default.class, to=BasicPostal.class) @ConvertGroup(from=Complete.class, to=FullPostal.class) Address > getAddresses() { [...] } }

验证容器元素

parameters: 形参 arguments: 实参

Constraint可以被用在泛型容器的元素上,比如:List、Map、Optional等,通过标注Constraint注解到类型实参(type arguments)上。 可以被校验的容器类型有:field、properties、方法和构造函数的入参、方法方式。

 

java

代码解读

复制代码

private List<@Email String> emails; public Optional<@Email String> getEmail() { } public Map<@NotNull String, @ValidAddress Address> getAddressesByType() { } public List<@NotBlank String> getMatchingRecords(List<@NotNull @Size(max=20) String> searchTerms) { } private Map<String, @NotEmpty List<@ValidAddress Address>> addressesByType;

容器验证不支持标注到类型形参(type parameters)、带有extends和implements的类型实参(type arguments)上。

 

java

代码解读

复制代码

public class NonNullList<@NotNull T> { } public class ContainerFactory { <@NotNull T> Container<T> instantiateContainer(T wrapped) { [...] } } public class NonNullSet<T> extends Set<@NotNull T> { }

有些不带类型实参的容器可以隐式获取类型:

 

java

代码解读

复制代码

@Min(1) private OptionalInt optionalNumber; @Negative private LongProperty negativeLong; @Positive private IntegerProperty positiveInt; private final ListProperty<@NotBlank StringProperty> notBlankStrings;

方法、构造函数校验

方法、构造函数校验可以直接添加Constraint注解到方法、构造函数上、对应的的参数上。 对于方法而言,可以校验请求参数,返回值,而对于构造函数而言,可以校验构造函数的入参。 方法不能为static、final,需要为public的。一般通过动态代理实现或者类似的。

验证参数

以下为参数校验:

 

java

代码解读

复制代码

public class OrderService { public OrderService(@NotNull CreditCardProcessor creditCardProcessor) { } public void placeOrder( @NotNull @Size(min=3, max=20) String customerCode, @NotNull Item item, @Min(1) int quantity) { } }

以下为跨参数校验,可以校验多个参数,使用上跟类级别的校验注解一样: 需要校验startDate需要早于endDate

 

java

代码解读

复制代码

public class CalendarService { @ConsistentDateParameters public void createEvent( String title, @NotNull Date startDate, @NotNull Date endDate) { } }

跨参数注解不能使用在没有参数的方法上,不然会抛出ConstraintDeclarationException异常。 在定义跨参数校验注解的时候,为了避免歧义,建议配置validationAppliesTo为ConstraintTarget.PARAMETERS。 为了在进行跨参数校验时获取参数的名称,提供了jakarta.validation.ParameterNameProvider当前正在代理的对应的参数的名称。

验证返回值

通过标注Constraint注解到方法、构造函数上实现返回值的校验。 有些Constraint可以同时校验参数、返回值。这种情况下最好建议配置validationAppliesTo到ConstraintTarget.RETURN_VALUE。

 

java

代码解读

复制代码

public class OrderService { private CreditCardProcessor creditCardProcessor; @ValidOnlineOrderService public OrderService(OnlineCreditCardProcessor creditCardProcessor) { this.creditCardProcessor = creditCardProcessor; } @ValidBatchOrderService public OrderService(BatchCreditCardProcessor creditCardProcessor) { this.creditCardProcessor = creditCardProcessor; } @NotNull @Size(min=1) public Set<CreditCardProcessor> getCreditCardProcessors() { [...] } @NotNull @Future public Date getNextAvailableDeliveryDate() { [...] } }

级联校验

通过使用@Valid实现方法参数、返回值内部引用的级联校验。如果参数、返回值是null,那么不会进行校验。

 

java

代码解读

复制代码

public class OrderService { @NotNull @Valid private CreditCardProcessor creditCardProcessor; @Valid public OrderService(@NotNull @Valid CreditCardProcessor creditCardProcessor) { this.creditCardProcessor = creditCardProcessor; } @NotNull @Valid public Order getOrderByPk(@NotNull @Valid OrderPK orderPk) { } @NotNull public Set<@Valid Order> getOrdersByCustomer(@NotNull @Valid CustomerPK customerPk) { } }

继承分层校验

有些时候需要对抽象方法进行校验(重写继承类、实现接口的方法),形成一个约定,此时可以对参数、返回值进行校验。 这种情况下有有一些规则需要满足:

  1. 基类可以在方法的入参、返回值增加Constraint校验标注
  2. 派生类在重写方法的时候,方法的入参不能再次设置Constraint校验注解(派生类的入参校验必须宽松于基类),方法的返回值可以增加校验(派生类的返回值校验必须严格于基类) 以下是错误的方式。派生类在入参进行了校验:
 

java

代码解读

复制代码

public interface OrderService { void placeOrder(String customerCode, Item item, int quantity); } public class SimpleOrderService implements OrderService { @Override public void placeOrder( @NotNull @Size(min=3, max=20) String customerCode, @NotNull Item item, @Min(1) int quantity) { } }

以下是正确的方式:

 

java

代码解读

复制代码

public class OrderService { Order placeOrder(String customerCode, Item item, int quantity) { [...] } } public class SimpleOrderService extends OrderService { @Override @NotNull @Valid public Order placeOrder(String customerCode, Item item, int quantity) { } }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值