领域参数校验(JSR-303 Bean Validation)

领域参数校验(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()

调用关系

2018091911.56.25

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的包下看到配置文件。

1IZl0OW0j19_35_01__09_20_2018

在javax.validation.spi.ValidationProvider文件中配置了ValidationProvider的实现

1Ia3yfIVA19_35_19__09_20_2018

我们也可以看到jdbc的驱动也是这种实现。

1IrhWepiW19_51_59__09_20_2018

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();//如果验证通过,则发布会空
	}
发布了121 篇原创文章 · 获赞 56 · 访问量 167万+

猜你喜欢

转载自blog.csdn.net/u013565163/article/details/82845788