大体意思是:Bean Validation 标准化了Java平台的约束定义、描述、和验证。
详细了解请参考:http://beanvalidation.org/
Bean Validation现在一个有两个规范:
1、Bean Validation 1.0(JSR-303)
定义了基于注解方式的JavaBean验证元数据模型和API,也可以通过XML进行元数据定义,但注解将覆盖XML的元数据定义。
详细了解请参考:http://jcp.org/en/jsr/detail?id=303
JSR-303主要是对JavaBean进行验证,如方法级别(方法参数/返回值)、依赖注入等的验证是没有指定的。因此又有了JSR-349规范的产生。
2、Bean Validation 1.1(JSR-349)
Bean Validation 标准化了Java平台的约束定义、描述、和验证。
此规范目前处于草案状态,详细了解请参考:http://jcp.org/en/jsr/detail?id=349.
该草案现在主要内容:
方法级别验证支持(验证方法参数和和返回值);
依赖注入验证的支持。
对Bean Validation的详细介绍可参考Bean Validation官网查看http://beanvalidation.org/。
Spring3.1目前已经完全支持依赖注入验证和方法级别验证的支持,只是不是原生的(规范还是草案)。
Bean Validation 1.0的参考实现有Hibernate Validator(下载地址:http://www.hibernate.org/subprojects/validator.html);1.1还处于草案状态。
上图摘自hibernate validator 参考文档,从图中可以看出,我们可以在任何位置实施验证。
1、表现层验证:SpringMVC提供对JSR-303的表现层验证;
2、业务逻辑层验证:Spring3.1提供对业务逻辑层的方法验证(当然方法验证可以出现在其他层,但笔者觉得方法验证应该验证业务逻辑);
3、DAO层验证:Hibernate提供DAO层的模型数据的验证(可参考hibernate validator参考文档7.3. ORM集成)。
4、数据库端的验证:通过数据库约束来进行;
5、客户端验证支持:JSR-303也提供编程式验证支持。
对于DAO层和客户端验证支持不在我们示例范围,忽略,感兴趣的同学可以参考《hibernate validator reference》(有中文)。
在测试支持大家需要准备好如下jar包:
validation-api-1.0.0.GA.jar
hibernate-validator-4.2.0.Final.jar
现在我们纯粹的只是利用Bean Validation和hibernate的实现Hibernate Validator来做一个校验
package com.somnus.validation.model; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; public class User { @NotNull @Pattern(regexp = "[a-zA-Z0-9_]{5,10}" , message = "{user.username.illegal}") private String username; @Size(min = 6, max=10) private String password; //省略setter/getter public User(String username, String password) { super(); this.username = username; this.password = password; } public User() { super(); } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } }
package com.somnus.validation; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import org.hibernate.validator.HibernateValidator; import org.junit.Test; import com.somnus.validation.model.User; public class ValidationTest { @Test public void defaultValidator(){ Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); User user = new User("adm#in","12345"); Set<ConstraintViolation<User>> constraintViolations = validator.validate(user); for(ConstraintViolation<User> data:constraintViolations){ System.out.println(data.getPropertyPath().toString() + data.getMessage()); } } @Test public void hibernateValidator(){ Validator validator = Validation.byProvider(HibernateValidator.class).configure() /*.failFast(true)*/ .buildValidatorFactory().getValidator(); User user = new User("adm#in","12345"); Set<ConstraintViolation<User>> constraintViolations = validator.validate(user); for(ConstraintViolation<User> data:constraintViolations){ System.out.println(data.getPropertyPath().toString() + data.getMessage()); } } }
我们可以通过看源码的方式,来了解其中的区别。
这里之所以要讲清楚为什么有这两种方式,其实也是为Spring框架对如果引入validator做铺垫,具体详情请看下文,并且找到相关bean的源码
我们如果不给这两个bean手动注入Validator,它也可以拿到hibernate提供的ValidatorImpl
另外我们通常也会在Spring项目中碰到有开发者会使用
然后就有了如下配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:message/validate"/> <property name="fileEncodings" value="utf-8"/> <property name="cacheSeconds" value="120"/> </bean> <bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <!-- 不设置则默认去找org.hibernate.validator.HibernateValidator--> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> <!--不设置则默认为classpath下的 ValidationMessages.properties --> <property name="validationMessageSource" ref="validatemessageSource"/> <!-- 不设置则默认为false,true和false的区别在于:如果为true则不管验证项有多少个为失败的, 都只返回解析到的第一个,其余再返回,如果为false则返回所有验证失败项 --> <property name="validationPropertyMap"> <map> <entry key="hibernate.validator.fail_fast" value="true"/> </map> </property> </bean> </beans>通过我写的注释,想必你已明白,貌似相关注入就算我全部不写这个validator也是可以被创建出来的,当然啦,按需配置吧
可以参考《SpringMVC(6)数据验证》。
此处不再阐述。
Spring3.0开始支持对依赖注入的依赖进行验证。Spring对依赖注入验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的BeanValidationPostProcessor。
示例:
1、Bean组件类定义
请参考上上面
2、开启依赖注入验证支持(spring-config-bean-validator.xml)
<bean class="org.springframework.validation.beanvalidation.BeanValidationPostProcessor"/>
3、Bean的XML配置定义(spring-config-bean-validator.xml)
<bean id="user" class="com.somnus.validation.model.User"> <property name="username" value="@"/> <property name="password" value="#"/> </bean>
@RunWith(value = SpringJUnit4ClassRunner.class) @ContextConfiguration(value = {"classpath:spring-config-bean-validator.xml"}) public class BeanValidatorTest { @Autowired User user; @Test public void test() { } }
5、运行测试后,容器启动失败并将看到如下异常:
java.lang.IllegalStateException: Failed to load ApplicationContext …… Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [spring-config-bean-validator.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: password - password.length.illegal; username - user.username.illegal …… Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: password - password.length.illegal; username - user.username.illegal
Spring3.1开始支持方法级别的验证。Spring对方法级别的验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的MethodValidationPostProcessor。
有了方法级别验证,我们就能够更加简单的在Java世界进行契约式设计了,关于契约式设计请参考《建造无错软件:契约式设计引论》。
没有MethodValidationPostProcessor之前我们可能这样验证:
public User get(Integer uuid) { //前置条件 Assert.notNull(uuid); Assert.isTrue(uuid > 0, "uuid must lt 0"); //获取 User Model Userl user = new User(); //此处应该从数据库获取 //后置条件 Assert.notNull(user); return user; }
有了MethodValidationPostProcessor之后我们可以这样验证:
public @NotNull User get(@NotNull @Size(min = 1) Integer uuid) { //获取 User Model User user = new User(); //此处应该从数据库获取 return user; }
前置条件的验证:在方法的参数上通过Bean Validation注解进行实施
后置条件的验证:直接在返回值上通过Bean Validation注解进行实施。
非常好,非常好,自此我们可以在Java世界进行更完美的契约式编程了。
示例:
1、Service类定义
package com.somnus.validation.service; import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import com.somnus.validation.model.User; @Service @Validated//告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持 public class ValidationServiceImpl{ public @NotNull User guess(@Valid User u) { // 获取 User Model User user = new User("admin","123456"); // 此处应该从数据库获取 if (!"admin".equals(u.getUsername())) {// 方便后置添加的判断(此处假设传入的用户名不为null 则返回null) return null; } return user; } }
<!--注册方法验证的后处理器--> <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
3、测试用例
package com.somnus.validation; import org.junit.Test; import com.somnus.AbstractTestSupport; import com.somnus.ApplicationContextHolder; import com.somnus.validation.model.User; import com.somnus.validation.service.ValidationServiceImpl; public class SpringTest extends AbstractTestSupport { @Test public void testPreCondtionFail(){ ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class); System.out.println(service.guess(new User("ad#min", "123456")));//不对的用户名,即前置条件不满足 } @Test public void testConditionSuccess(){ ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class); System.out.println(service.guess(new User("ad#min", "123456")));//正常流程 } @Test public void testPostCondtionFail(){ ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class); System.out.println(service.guess(new User("somnus", "123456")));//不满足后置条件的返回值 } }
注意,在使用方法级别验证时:
1、由于Bean Validation1.1正处于草案状态,Spring3.1无法支持原生的Bean Validation1.1,在未来的Bean Validation1.1发布时会直接使用原生的。
2、Spring3.1需要使用Hibernate Validator 4.2及更高版本。