Seam Bean验证快速进阶II:自定义约束

Seam Bean验证快速进阶II:自定义约束

由于百度空间不支持自己在文章中写css代码,所以排版非常简单。优化排版版本请参见:

http://blog.csdn.net/stenlylee/archive/2008/06/24/2583476.aspx

本文为Bean验证系列的第二部分。总体介绍请阅读这篇文章。本文主要介绍了约束定义。

可以通过内置约束(例如@NotNull、@Length等)来进行Bean验证,本文介绍的主要部分为基础验证的扩展。我们非常鼓励开发人员针对具体的业务需求编写自定义约束。

编写自定义约束

由于编写自定义约束是本文的关键部分,所以我们将主要关注于尽可能地简化相关操作。下面让我们开始吧。

正如我们在第一部分中看见的,约束主要由两个部分组成:

1、注释(annotation)
2、实现(implementation)

约束注释的定义

每个约束对应一个注释。你可以把注释当作一个安全的别名和一个描述符。我们可以在声明约束注释的时候添加一个以上的参数,用以实现各种不同的自定义行为。

 

public class Order {
    @NotNull @OrderNumber private String   number;
    @Range(min=0) private BigDecimal totalPrice;
    ...
}

 
我们来看一下@OrderNumber注释的定义:

 

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@ConstraintValidator(OrderNumberValidator.class)
public @interface OrderNumber {
    String message() default "{error.orderNumber}"; 
    String[] groups() default {}; 
}

 
约束注释只比一般的注释多了一点东西:

必须使用运行时拦截策略:Bean验证会在运行时检查你的对象

必须使用@ConstraintValidator注释

必须有message属性

必须有groups属性

@ConstraintValidator表示了这个Bean验证类是一个约束注释。它同样也是实现约束注释的惯例(我们随后会详细介绍它)。

message属性(通常赋予一个默认的关键字)用来覆盖默认的验证错误列表。我们会在以后的文章中详细描述这一部分。

groups属性定义了一些列子约束。它实现了部分验证和按照指定顺序验证。我们会在以后的文章中详细描述这一部分。

在这两个必备属性之外, 我们可以根据具体的验证逻辑来定义一些参数。这些参数会被传递给具体的约束实现。例如,一个@Range(范围)注释可能会需要一个最小值属性和一个最大值属性。

 

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@ConstraintValidator(RangeValidator.class)
public @interface Range {
    long max() default Long.MAX_VALUE;
    long min() default Long.MIN_VALUE;
    String message() default "{error.range}";
    String[] groups() default {}; 
}

 
现在,我们能够很快建立一个包含参数的约束了。下面我们需要一些具体的验证逻辑。

约束的实现

约束的实现类与约束的注释接口通过@ConstraintValidator关联。在早先的开发草案中,@ValidatorClass有时用来替代@ConstraintValidator:最终我们改变了这种错误的方式,对不起。约束实现类必须实现一个非常简单的接口Constraint<A extends Annotation>,这里A是一个目标约束注释。


public class OrderNumberValidator implements Constraint {
    public void initialize(OrderNumber constraintAnnotation) {
    //no initialization needed
    }

    /**
     * Order number are of the form Nnnn-nnn-nnn when n is a digit
     * The sum of each nnn numbers must be a multiple of 3
     */
    public boolean isValid(Object object) {
        if ( object == null) return true;
        if ( ! (object instanceof String) )
       throw new IllegalArgumentException("@OrderNumber only applies to String");
        String orderNumber = (String) object;
        if ( orderNumber.length() != 12 ) return false;
        if ( orderNumber.charAt( 0 ) != 'N'
            || orderNumber.charAt( 4 ) != '-'
            || orderNumber.charAt( 8 ) != '-'
        ) return false;
        try {
            long result = Integer.parseInt( orderNumber.substring( 1, 4 ) )
                + Integer.parseInt( orderNumber.substring( 5, 8 ) )
                + Integer.parseInt( orderNumber.substring( 9, 12 ) );
            return result % 3 == 0;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }
}

 

 

initialize方法通过参数的方式接收约束注释。这个方法一般会执行以下任务:

为isValid方法准备参数

如果需要的话,获得一些扩展资源

正如你所见,这个接口将注意力完全放在了验证逻辑上,将其他所有烦人的部分例如错误显示交给了bean验证提供者。

isValid负责验证具体的值。一些有趣的东西值得我们关注下:

isValid方法必须支持并发调用

当传入的对象类型和预期的不一样,必须抛出错误

null不被认为是无效的:规范文档中推荐将各种验证分离,如果一个值不能为空,则用@NotNull来限制

这些简单的定义让程序员能够自由编写约束进行验证

允许多次使用同种约束

特别需要说明的,在使用groups的时候,你有时可能需要对同一个元素多次应用同一种约束。Bean验证规范特别考虑了这种包含一组约束注释的注释类型。

 

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Patterns {
    Pattern[] value();
}

@ConstraintValidator(PatternValidator.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Pattern {
    /** regular expression */
    String regex();

    /** regular expression processing flags */
    int flags() default 0;

    String message() default "{validator.pattern}";
        
    String[] groups() default {};
}

 
在这个例子中,你可能会需要对同一个属性多次使用这种约束验证。

 

public class Engine {
    @Patterns( {
        @Pattern(regex = "^[A-Z0-9-]+$", message = "must contain alphabetical characters only"),
        @Pattern(regex = "^....-....-....$", message="must match ....-....-....")
    } )
    private String serialNumber;
    ...

 
构建约束

默认情况下,Bean验证使用无参构造器实例化约束验证实现类。然而,规范也提供了一个扩展,该扩展将指向实例化进程的指针提供给其他依赖的管理工具库,例如Web Beans,Guice,Spring,JBoss Seam甚至JBoss Microcontainer。

根据不同管理工具的功能,我们期望在需要的时候验证的实现类能够接受到注入的资源:这个功能的实现完全取决于所依赖的管理工具。

类级约束

一些人可能会比较担心,能否实现跨越多个属性的验证,或者能否实现直接基于许多属性的约束。最典型的莫过于地址验证。地址通常需要复杂的规则来验证:

街道的名字还稍微能够确定,并且肯定能够进行长度限制

不同国家的邮编构成几乎都不一样

城市通常与邮编互相关联,因此能够实现一些错误检查(假设能够通过一种验证服务来完成)

由于这些互相依赖的条件,一个简单的属性级别的验证很难满足这种需求

Bean验证规范提供的解决方案涵盖了这两个方面:

它能够强制要求一组约束执行后,再执行组(groups)和组员次序(group sequences)内规定的另外一组约束。这个主体将在下一篇博客文章中介绍。

它允许定义类级别的约束

类级别的约束是规定不变的约束(注释和实现),它被应用在class上而不是属性上。换一种说法,类级别的约束接受的是实例化的对象,而不是属性值,即传递给isValid方法的参数。

 

@Address 
public class Address {
    @NotNull @Max(50) private String street1;
    @Max(50) private String street2;
    @Max(10) @NotNull private String zipCode;
    @Max(20) @NotNull String city;
    @NotNull private Country country;
    ...
}

@ConstraintValidator(MultiCountryAddressValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
public @interface Address {
    String message() default "{error.address}";
    String[] groups() default {};
}

public class MultiCountryAddressValidator implements Constraint {
    public void initialize(Address constraintAnnotation) {
        //initialize the zipcode/city/country correlation service
    }

    /**
     * Validate zipcode and city depending on the country
     */
    public boolean isValid(Object object) {
        if ( ! (object instanceof Address) )
            throw new IllegalArgumentException("@Address only applies to Address");
        Address address = (Address) object;
        Country country = address.getCountry();
        if ( country.getISO2() == "FR" ) {
            //check address.getZipCode() structure for France (5 numbers)
            //check zipcode and city correlation (calling an external service?)
            return isValid;
        } else if ( country.getISO2() == "GR" ) {
            //check address.getZipCode() structure for Greece
            //no zipcode / city correlation available at the moment
            return isValid;
        }
         ...
    }
}

 
我们将高级的验证规则写在地址对象的前面,并且通过MultiCountryAddressValidator来实现它。通过处理这个实例化的对象,类级别的验证拥有更大的灵活性,并且能够对相互关联的多个属性进行验证。注意,这里的命令放在了等式的外面(Note that ordering is left out of the equation here),我们会在下一篇文章中回到这个例子。

专家组已经针对各种不同的多属性验证进行了多次讨论:我们认为类级别的约束验证相对其他属性级别的约束验证(包括属性依赖关系验证方式)更加简单和灵活。我们同样欢迎你的任何反馈。

结束语

自定义约束为JSR 303 Bean Validation灵活性表现的核心。编写自定义约束肯定不会被认为是一件困难的事情:

这种验证规则会是你所期望的最严谨验证语法

在代码中小心使用注释名称,将会让你的程序非常易读

如果你有什么想法,请告诉我们。你可以下载完整的规范草案。下一次发表的博客文章将介绍groups、约束子集和验证顺序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值