5.Spring 里的数据校验

 

Spring 里的数据校验

 

 

相信大家都知道什么是数据校验吧,简单说就是对数据处理前进行验证,包括有效性验证,格式验证,完整性验证,等等。Spirng对此主要提供了两种验证支持:


1.使用spring validator 接口


2.使用JSR-303, Bean Validation API


下面让我们一个一个来看:

使用spring validator 接口:这种方式主要是通过实现Validator接口。假设有个Contact类,里面的first name 属性不能为空, 代码如下

 

package com.apress.prospring3.ch14.domain;

import java.net.URL;
import org.joda.time.DateTime;

public class Contact {
	private String firstName;
	private String lastName;
	private DateTime birthDate;
	private URL personalSite;

	
	// Getter/setter methods omitted
	public String toString() {
		return "First name: " + getFirstName() + " - Last name: "
				+ getLastName() + " - Birth date: " + getBirthDate()
				+ " - Personal site: " + getPersonalSite();
	}
}
  

对此我们来创建一个数据校验类,代码如下:

 

package com.apress.prospring3.ch14.validator;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.apress.prospring3.ch14.domain.Contact;

@Component("contactValidator")
public class ContactValidator implements Validator {
	public boolean supports(Class<?> clazz) {
		return Contact.class.equals(clazz);
	}

	public void validate(Object obj, Errors e) {
		ValidationUtils.rejectIfEmpty(e, "firstName", "firstName.empty");
	}
}
  

可以看到这个类实现了接口Validator的2个方法:support()代表这个validator类所支持的类型;validate()方法用来对传入的对象进行校验,返回的结果会存入org.springframework.validation.Errors。可以看到在上面我们使用了spring自带的校验方法ValidationUtils.rejectIfEmpty()来校验属性是否为空,最后的firstName.empty表示错误代码,可以被用于国际化。


接下来需要配置下spring,因为配置了标注,只需让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" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.apress.prospring3.ch14.validator" />
</beans>

 

这样一个简单的validator就写完了,下面是测试代码:

 

package com.apress.prospring3.ch14.validator;

import java.util.List;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.apress.prospring3.ch14.domain.Contact;

public class SpringValidatorSample {
	public static void main(String[] args) {
		GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
		ctx.load("classpath:spring-validator-app-context.xml");
		ctx.refresh();
		Contact contact = new Contact();
		contact.setFirstName(null);
		contact.setLastName("Ho");
		Validator contactValidator = ctx.getBean("contactValidator",
				Validator.class);
		BeanPropertyBindingResult result = new BeanPropertyBindingResult(
				contact, "Clarence");
		ValidationUtils.invokeValidator(contactValidator, contact, result);
		List<ObjectError> errors = result.getAllErrors();
		System.out.println("No of validation errors: " + errors.size());
		for (ObjectError error : errors) {
			System.out.println(error.getCode());
		}
	}
}
  

可以看见测试代码里创建的Contact对象的firstName=null。为了捕获校验结果,我们创建了BeanPropertyBindingResult类,并且使用了ValidationUtils.invokeValidator()来手动调用validator。跑完程序结果应该如下:

 

No of validation errors: 1
firstName.empty

 

使用JSR-303, Bean Validation API

 

Spring 3对JSR-303的支持非常棒,Bean Validation API里定义了大量的校验标注,使用起来非常方便。比如@NotNull。

口说无凭,还是通过例子来学习,我们创建一个Customer类:

 

package com.apress.prospring3.ch14.domain;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class Customer {
	@NotNull
	@Size(min = 2, max = 60)
	private String firstName;
	private String lastName;
	@NotNull
	private CustomerType customerType;
	private Gender gender;

	// Getter/setter methods omitted
	public boolean isIndividualCustomer() {
		return this.customerType.equals(CustomerType.INDIVIDUAL);
	}
}
  

可以看到,我们在属性上定义了@NotNull,用来表示这个属性不能为空。以及@Size表示这个属性的长度必须为大于2小于60.对了,里面还用到了CustomerType和Gender,代码如下:

 

package com.apress.prospring3.ch14.domain;

public enum CustomerType {
	INDIVIDUAL("I"), CORPORATE("C");
	private String code;

	private CustomerType(String code) {
		this.code = code;
	}

	public String toString() {
		return this.code;
	}
}
 
package com.apress.prospring3.ch14.domain;

public enum Gender {
	MALE("M"), FEMALE("F");
	private String code;

	private Gender(String code) {
		this.code = code;
	}

	public String toString() {
		return this.code;
	}
}
  

老样子,接下来该编写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" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.apress.prospring3.ch14.jsr303.service" />
	<bean id="validator"
		class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
</beans>
  

唯一不同就是我们添加了一个LocalValidatorFactoryBean的validator bean。为了能够调用,我们最好再加个简单的校验service,如下:

 

package com.apress.prospring3.ch14.jsr303.service;

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.apress.prospring3.ch14.domain.Customer;

@Service("myBeanValidationService")
public class MyBeanValidationService {
	@Autowired
	private Validator validator;

	public Set<ConstraintViolation<Customer>> validateCustomer(Customer customer) {
		return validator.validate(customer);
	}
}
 

在sevice类里我们注入了一个validator,一旦LocalValidatorFactoryBean被定义了,你可以在任何地方注入这个bean。执行校验也很简单,调用validate方法即可,校验的结果将被封装为ConstraintViolation<T>对象集合返回。OK,让我们测试,代码如下:

 

package com.apress.prospring3.ch14.jsr303;

import java.util.HashSet;
import java.util.Set;
import javax.validation.ConstraintViolation;
import org.springframework.context.support.GenericXmlApplicationContext;
import com.apress.prospring3.ch14.domain.Customer;
import com.apress.prospring3.ch14.domain.CustomerType;
import com.apress.prospring3.ch14.jsr303.service.MyBeanValidationService;

public class Jsr303Sample {
	public static void main(String[] args) {
		GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
		ctx.load("classpath:jsr303-app-context.xml");
		ctx.refresh();
		MyBeanValidationService myBeanValidationService = ctx.getBean(
				"myBeanValidationService", MyBeanValidationService.class);
		Customer customer = new Customer();
		// Test basic constraints
		customer.setFirstName("C");
		customer.setLastName("Ho");
		customer.setCustomerType(null);
		customer.setGender(null);
		validateCustomer(customer, myBeanValidationService);
	}

	private static void validateCustomer(Customer customer,
			MyBeanValidationService myBeanValidationService) {
		Set<ConstraintViolation<Customer>> violations = new HashSet<ConstraintViolation<Customer>>();
		violations = myBeanValidationService.validateCustomer(customer);
		listViolations(violations);
	}

	private static void listViolations(
			Set<ConstraintViolation<Customer>> violations) {
		System.out.println("No. of violations: " + violations.size());
		for (ConstraintViolation<Customer> violation : violations) {
			System.out.println("Validation error for property: "
					+ violation.getPropertyPath() + " with value: "
					+ violation.getInvalidValue() + " with error message: "
					+ violation.getMessage());
		}
	}
}
  

测试代码没什么复杂的,可以看见直接调用了validateCustomer()方法,结果正如我们期望的,有2条校验失败记录:

 

No. of violations: 2
Validation error for property: firstName with value: C with error message: size must be
between 2 and 60
Validation error for property: customerType with value: null with error message: may not be
Null
  

创建自定义的Validator

 

虽然JSR-303很给力,但有些比较特别的需求还是得自己来定义,下面我们来展示下如何通过自定义的方法创建Validator。自定义的步骤主要分2步,首先自定义一个validate标注,然后编写这个标注的实现代码。例子来了,这次我们稍微做下变动,提个扭曲的需求。还是刚才的Customer对象,lastName和Gender不能为空。首先自定义标注:

 

package com.apress.prospring3.ch14.jsr303.validator;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Constraint(validatedBy = IndividualCustomerValidator.class)
@Documented
public @interface CheckIndividualCustomer {
	String message() default "Individual customer should have gender and last name defined";

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

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

里面有很多标注,我们来解释下。@Target(ElementType.TYPE)表示我们的标注只能用于class这一层级。@Constraint代表这是一个校验用标注,里面的validatedBy标识了哪个实现类来实现校验逻辑,这里是IndividualCustomerValidator。

然后是里面的三个属性:

 

Message定义了返回的错误信息,当然也可以通过标注的方式定义。

Groups用于定义校验用于哪些校验小组。

Payload用于添加一些额外的约束信息(比如校验顺序神马的)


OK,下面是第二步,创建实现:

 

package com.apress.prospring3.ch14.jsr303.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.apress.prospring3.ch14.domain.Customer;

public class IndividualCustomerValidator implements
		ConstraintValidator<CheckIndividualCustomer, Customer> {
	public void initialize(CheckIndividualCustomer constraintAnnotation) {
	}

	public boolean isValid(Customer customer, ConstraintValidatorContext context) {
		boolean result = true;
		if (customer.getCustomerType() != null
				&& (customer.isIndividualCustomer() && (customer.getLastName() == null || customer
						.getGender() == null))) {
			result = false;
		}
		return result;
	}
}
  

可以发现,IndividualCustomerValidator实现了ConstraintValidator<CheckIndividualCustomer, Customer>,也就是说这个校验类是用@CheckIndividualCustomer标注来校验Customer的。方法isValid()用来存放校验逻辑,校验customer对象,并通过返回boolean类型来代表通过还是失败。

好了,到此为止,自定义已经完成了。现在我们只需要在原来的Customer类添加上我们自定义的标注@CheckIndividualCustomer即可了:

 

package com.apress.prospring3.ch14.domain;
// Import statements omitted
@CheckIndividualCustomer
public class Customer {
// Other code omitted
}

测试代码片段:

customer.setFirstName("Clarence");
customer.setLastName("Ho");
customer.setCustomerType(CustomerType.INDIVIDUAL);
customer.setGender(null);
validateCustomer(customer, myBeanValidationService);

  

运行结果:

No. of violations: 1
Validation error for property: with value: com.apress.prospring3.ch14.domain.Customer@d3f136e
with error message: Individual customer should have gender and last name defined	
  

用AssertTrue来自定义Validator

 

除了自定义标注的方式外,JSR-303还能通过@AssertTrue来自定义validator。

例子来了,还是刚才的Customer类,把@CheckIndividualCustomer删除,在isValidIndividualCustomer()方法上添加如下@AssertTrue,别加错地方了。如下代码片段:

 

// Codes omitted
	@AssertTrue(message = "Individual customer should have gender and last name defined")
	private boolean isValidIndividualCustomer() {
		boolean result = true;
		if (getCustomerType() != null
				&& (isIndividualCustomer() && (gender == null || lastName == null)))
			result = false;
		return result;
	}
  

当调用validate()时,系统会检查这个方法是否返回true,如果不是则抛出错误message。运行一下,结果应该是一样的。

 

最后,总结下这么多的自定义校验方式究竟该使用哪种。从易用性来说,@AssertTrue无疑是最简单的,如果你的逻辑够简单,那就用它。但是如果逻辑比较复杂,那就要用自定义标注或者实现spring validation 接口的方法了,仅此而已,希望对各位有用。


下一节: Spring Remoting (-) http://wsjjasper.iteye.com/blog/1574590

你可能感兴趣的:(spring,类型转换,validate,程序设计,校验)