领域参数校验(JSR-303 Bean Validation)
JSR-303的目标
JSR(java specification requests) 303是java对bean参数校验的规则,官方已经实现了整套api,你也可以根据自己的需求根据接口实现自己的bean validation,我们一般开发中实现自己的参数验证器就可以了。
如果没有303(以下JSR-303简称303),那我们的参数校验会分散在项目的各个层,例如 控制层、持久层可能会重复实现对某个参数的校验。这样的代码违反了我们的封装,及复用性。
使用303我们只需要在领域层对具体的属性(域)使用注解就可以进行验证参数的合法性。
public class Person{
private String name;
private int age;
//get and set method
}
/**
* 简陋的控制器,表明效果
**/
@RestController
@RequestMapping("/controller")
public class Controller{
@RequestMapping("/")
public String index(Person person){
//验证name属性不能为空
if(person.getName() == null || "".equals(person.getName()) ){}
//处理参数非法
}
//handler something
}
Please describe the proposed Specification:
Validating data is a common task that is copied in many different layers of an application, from the presentation tier to the persistentce layer. Many times the exact same validations will have to be implemented in each separate validation framework, proving time consuming and error-prone. To prevent having to re-implement these validations at each layer, many developers will bundle validations directly into their classes, cluttering them with copied validation code that is, in fact, meta-data about the class itself.
This JSR will define a meta-data model and API for JavaBean validation. The default meta-data source will be annotations, with the abilty to override and extend the meta-data through the use of XML validation descriptors. It is expected that the common cases will be easily accomplished using the annotations, while more complex validations or context-aware validation configuration will be available in the XML validation descriptors.
The validation API developed by this JSR will not be specific to any one tier or programming model. It will specifically not be tied to either the web tier or the persistence tier, and will be available for both server-side application programming, as well as rich client Swing application developers. This API is seen as a general extension to the JavaBeans object model, and as such is expected to be used as a core component in other specifications, such as JSF, JPA, and Bean Binding.
默认提供的 校验参数
Constraint | Description | Example |
---|---|---|
@AssertFalse | 被注解的属性必须是false | @AssertFalse boolean isUnsupported; |
@AssertTrue | 被注解的属性必须是true | @AssertTrue boolean isActive; |
@DecimalMax | 被注解的属性必须是10进制值小于或等于注解中的值 | @DecimalMax(“30.00”) BigDecimal discount; |
@DecimalMin | 被注解的属性必须是10进制值大于等于注解中的值 | @DecimaMin(“5.00”) BigDecimal discount; |
@Digits | 被注解的属性必须在指定的精确度范围内 integer整数部分的位数,fraction小数部分的位数 | @Digits(integer=6, fraction=2) BigDecimal price; |
@Future | 被注解的属性必须是日期并且时间必须在当前时间之后 | @Future Date eventDate; |
@Max | 被注解的属性必须是一个整数并且小于等于注解中的值 | @Max(5) int quatity; |
@Min | 被注解的属性必须是一个整数并且大于等于注解中的值 | @Min(5) int quatity; |
@NotNull | 被注解的属性必须不能为null | @NotNull String username; |
@Null | 被注解的属性必须是null | @Null String unusedString; |
@Past | 被注解的必须是日期并且时间必须在当前时间之前 | @Past Date birthday; |
@Pattren | 被注解的属性必须能够匹配到正则注解中的正则表达式 | @Pattern(regexp= “\(\d{3}\)\d{3}-\d{4}”) String phoneNumber; |
@Size | 被注解属性的大小,并且匹配限制,如果被注解的属性是字符类型,则限制字符的长度,如果是Collection则是Colletion的size | @Size(min=2, max =240) String briefMessage |
使用jsr-303提供的参数校验
属性使用一个注解
public class Name{
@NotNull
private String firstName;
@NotNull
private String lastName;
}
属性使用多个注解
public class Name{
@NotNull
@Size(min=1, max=16)
private String firstname;
@NotNull
@Size(min=1, max=16)
private String lastname;
}
自定义参数校验
声明一个注解(简单示例:这里用来判断字符串是不是包含12)
StringValidaExample 是我们的验证规则处理器
package com.xuelongjiang.vailddemo.annotation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @author xuelongjiang
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = { StringValidaExample.class }
)
public @interface StringValida {
String message () default "字符不正确";
Class<?> [] groups() default {};
Class<? extends Payload> [] payload () default {};
}
声明注解验证器
package com.xuelongjiang.vailddemo.annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author xuelongjiang
*/
public class StringValidaExample implements ConstraintValidator<StringValida, String>{
@Override
public void initialize(StringValida constraintAnnotation) {
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if(s.contains("12")){//如果字符串包含12 则验证通过
return true;
}
return false;
}
}
Spring中验证领域的属性
注解属性
这里使用lombok为我们生成get和set方法
package com.xuelongjiang.vailddemo.domain;
import com.xuelongjiang.vailddemo.annotation.StringValida;
import lombok.Data;
/**
* @author xuelongjiang
*/
@Data
public class User {
@StringValida
private String name;
private String password;
}
在控制器验证参数
@Validated 注解领域表示需要对领域的属性进行验证,如果领域有验证注解
BindingResult bind 验证领域属性规则是否合法
/**
* @author xuelongjiang
*/
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/", method = RequestMethod.POST)
public User index(@RequestBody @Validated User user, BindingResult bind){
/* ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
System.out.println(validator.getClass());
if(violations.size() == 0 ){
System.out.println("验证通过");
}else {
System.out.println("验证不通过");
}*/
if(bind.hasErrors()){
System.out.println(bind.getFieldError().getDefaultMessage());
}
return user;
}
}
调用原生API
不依赖Spring,我们使用303提供的API来做参数验证,虽然代码看起来比较繁琐,但是这有助于我们理解事情的本质。
/**
* @author xuelongjiang
*/
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/", method = RequestMethod.POST)
public User index(@RequestBody User user ){
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
System.out.println(validator.getClass());
if(violations.size() == 0 ){
System.out.println("验证通过");
}else {
System.out.println("验证不通过");
}
/* if(bind.hasErrors()){
System.out.println(bind.getFieldError().getDefaultMessage());
}*/
return user;
}
}
原生源码分析
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();// 获取具体的工厂
Validator validator = factory.getValidator();// 获得验证器Validator
Set<ConstraintViolation<User>> violations = validator.validate(user);//进行验证获得结果
Validation.buildDefaultValidatorFactory()
调用关系
Validation类的结构
代码分析
public static ValidatorFactory buildDefaultValidatorFactory() {
return byDefaultProvider().configure().buildValidatorFactory();
}
public static GenericBootstrap byDefaultProvider() {
return new GenericBootstrapImpl();
}
configure
从上面可以看出实际调用的是GenericBootstrapImpl类的configure方法下面我们重点分析configure()方法
public Configuration<?> configure() {
ValidationProviderResolver resolver = this.resolver == null ?
getDefaultValidationProviderResolver() :
this.resolver; // step 1
List<ValidationProvider<?>> validationProviders;
try {
validationProviders = resolver.getValidationProviders(); // step 2
}
// don't wrap existing ValidationExceptions in another ValidationException
catch ( ValidationException e ) {
throw e;
}
// if any other exception occurs wrap it in a ValidationException
catch ( RuntimeException re ) {
throw new ValidationException( "Unable to get available provider resolvers.", re );
}
if ( validationProviders.isEmpty() ) { // step 3
String msg = "Unable to create a Configuration, because no Bean Validation provider could be found." +
" Add a provider like Hibernate Validator (RI) to your classpath.";
throw new NoProviderFoundException( msg );
}
Configuration<?> config;
try {
config = resolver.getValidationProviders().get( 0 ).createGenericConfiguration( this ); // step 4
}
catch ( RuntimeException re ) {
throw new ValidationException( "Unable to instantiate Configuration.", re );
}
return config;
}
}
step 1
ValidationProviderResolver resolver = this.resolver == null ?
getDefaultValidationProviderResolver() :
this.resolver;
this.resolver
为null调用getDefaultValidationProviderResolver()
方法
public ValidationProviderResolver getDefaultValidationProviderResolver() {
if ( defaultResolver == null ) {
defaultResolver = new DefaultValidationProviderResolver();//实际调用方法
}
return defaultResolver;
}
private static class DefaultValidationProviderResolver implements ValidationProviderResolver {
@Override
public List<ValidationProvider<?>> getValidationProviders() {
// class loading and ServiceLoader methods should happen in a PrivilegedAction
return GetValidationProviderListAction.getValidationProviderList();
}
}
上述代码实际调用getValidationProviderList方法,由于只需要实现类只需要加载一次,所有需要使用synchronized
进行加锁。
public static synchronized List<ValidationProvider<?>> getValidationProviderList() {
if ( System.getSecurityManager() != null ) {
return AccessController.doPrivileged( INSTANCE );
}
else {
return INSTANCE.run();
}
}
调用INSTANCE.run()
方法
INSTANCE为GetValidationProviderListAction的实例。
private final static GetValidationProviderListAction INSTANCE = new GetValidationProviderListAction();
List<ValidationProvider<?>> validationProviderList = loadProviders( classloader );//核心方法,返回ValidationProvider的实现,获得ValidatorFactory
private List<ValidationProvider<?>> loadProviders(ClassLoader classloader) {
ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );//加载HibernateValidator类通过SPI技术
Iterator<ValidationProvider> providerIterator = loader.iterator();
List<ValidationProvider<?>> validationProviderList = new ArrayList<>();
while ( providerIterator.hasNext() ) {
try {
validationProviderList.add( providerIterator.next() );
}
catch ( ServiceConfigurationError e ) {
// ignore, because it can happen when multiple
// providers are present and some of them are not class loader
// compatible with our API.
}
}
return validationProviderList;
}
上述代码获得了HibernateValidator类,我们通过这个类产生了ValidatorFactory类。
SPI(service provider Interface)
是java6引入的一个新的技术,一种动态加载实现的技术。
实际是 基于接口编程 + 策略模式 + 配置文件。可以在validator的包下看到配置文件。
在javax.validation.spi.ValidationProvider文件中配置了ValidationProvider
的实现
我们也可以看到jdbc的驱动也是这种实现。
step 2
validationProviders = resolver.getValidationProviders();
通过step 1 我们获得了resolver
并且getValidationProviders()
方法返回HibernateValidator
集合。
step 3
if ( validationProviders.isEmpty()
不为空,进入step 4
step 4
config = resolver.getValidationProviders().get( 0 ).createGenericConfiguration( this );
调用HibernateValidator
的方法,并且GenericBootstrapImpl
作为方法传入参数。
HibernateValidator
public class HibernateValidator implements ValidationProvider<HibernateValidatorConfiguration> {
@Override
public HibernateValidatorConfiguration createSpecializedConfiguration(BootstrapState state) {
return HibernateValidatorConfiguration.class.cast( new ConfigurationImpl( this ) );
}
@Override
public Configuration<?> createGenericConfiguration(BootstrapState state) {
return new ConfigurationImpl( state );
}
@Override
public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) {
return new ValidatorFactoryImpl( configurationState );
}
}
最终调用buildValidatorFactory
方法返回ValidatorFactory方法。
validator.validate(user)
validator.validate核心调用方法是如下的方法:
isValid = validator.isValid( validatedValue, constraintValidatorContext );
这里实际调用的是我们的isValid方法
public class StringValidaExample implements ConstraintValidator<StringValida, String>{
@Override
public void initialize(StringValida constraintAnnotation) {
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if(s.contains("12")){
return true;
}
return false;
}
}
protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
ValueContext<?, ?> valueContext,
ConstraintValidatorContextImpl constraintValidatorContext,
ConstraintValidator<A, V> validator) {
boolean isValid;
try {
@SuppressWarnings("unchecked")
V validatedValue = (V) valueContext.getCurrentValidatedValue();
isValid = validator.isValid( validatedValue, constraintValidatorContext );//验证校验是否通过
}
catch (RuntimeException e) {
if ( e instanceof ConstraintDeclarationException ) {
throw e;
}
throw LOG.getExceptionDuringIsValidCallException( e );
}
if ( !isValid ) {
//We do not add these violations yet, since we don't know how they are
//going to influence the final boolean evaluation
return executionContext.createConstraintViolations(
valueContext, constraintValidatorContext
);//如果验证不通过,则返回验证结果
}
return Collections.emptySet();//如果验证通过,则发布会空
}