Spring3.1 对Bean Validation规范的新支持(方法级别验证)

一、Bean Validation框架简介

Bean Validation standardizes constraint definition, declaration and validation for the Java platform.

大体意思是:Bean Validation 标准化了Java平台的约束定义、描述、和验证。

 

详细了解请参考:http://beanvalidation.org/

 

Bean Validation现在一个有两个规范:

 

1、Bean Validation 1.0(JSR-303)


This JSR will define a meta-data model and API for JavaBeanTM validation based on annotations, with overrides and extended meta-data through the use of XML validation descriptors.

定义了基于注解方式的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 standardizes constraint definition, declaration and validation for the Java platform.

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还处于草案状态。

 

二、Bean Validation在开发中的位置


Spring3.1 对Bean Validation规范的新支持(方法级别验证)_第1张图片

 


Spring3.1 对Bean Validation规范的新支持(方法级别验证)_第2张图片

 

 

上图摘自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);   
    } 
}

调用 JSR 303 API 进行校验

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());
		}
	}
}

针对上述我给出的两组测试,区别只在于如何获得Validator,从代码上看去差别挺大,其实本质上没有任何区别,都是获得ValidationImpl

Spring3.1 对Bean Validation规范的新支持(方法级别验证)_第3张图片

我们可以通过看源码的方式,来了解其中的区别。


这里之所以要讲清楚为什么有这两种方式,其实也是为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也是可以被创建出来的,当然啦,按需配置吧

四、Spring3.0支持表现层验证

可以参考《SpringMVC(6)数据验证》。

 

此处不再阐述。

 

五、Spring3.0支持依赖注入验证(Bean Validation 1.1草案)

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>  

4、测试用例

    @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支持方法级别验证(Bean Validation 1.1草案)

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;
	}
}


2、开启Spring3.1对方法级别验证支持(spring-config-method-validator.xml)

    <!--注册方法验证的后处理器-->  
    <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")));//不满足后置条件的返回值  
	}
	
}



通过如上测试,我们可以看出Spring3.1已经非常好的支持契约式编程了。

 

注意,在使用方法级别验证时:

1、由于Bean Validation1.1正处于草案状态,Spring3.1无法支持原生的Bean Validation1.1,在未来的Bean Validation1.1发布时会直接使用原生的。

2、Spring3.1需要使用Hibernate Validator 4.2及更高版本。


你可能感兴趣的:(spring,bean,validation)