Spring整合Bean Validation

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring整合Bean Validation相关的知识,希望对你有一定的参考价值。

Spring整合Bean Validation


Bean Validatin专栏已经大致介绍了关于Java Bean数据校验的规范体系,不清楚的可以先去看看


引言

Spring提供了对Bean Validation的支持,具体类体现在org.springframework.validation包下面:

说明:这个包所在的jar是spring-context,属于Spring上下文的核心功能模块

Spring虽然没有直接实现Bean校验这块的JSR规范,但是从Spring3.0开始,Spring就提供了对Bean Validation的支持。

  • 3.0提供了Bean级别的校验
  • 3.1提供了更加强大的方法级别的校验

BeanValidationPostProcessor

它就是个普通的BeanPostProcessor。它能够去校验Spring容器中的Bean,从而决定允不允许它初始化完成。

比如我们有些Bean某些字段是不允许为空的,比如数据的链接,用户名密码等等,这个时候用上它处理就非常的优雅和高级了

若校验不通过,在违反约束的情况下就会抛出异常,阻止容器的正常启动。

public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean 
	// 这就是我们熟悉的校验器
	// 请注意这里是javax.validation.Validator,而不是org.springframework.validation.Validator
	@Nullable
	private Validator validator;
	// true:表示在Bean初始化之后完成校验
	// false:表示在Bean初始化之前就校验
	//这里表示初始化指的是初始化方法被调用,initMethod方法和InitializingBean接口
	private boolean afterInitialization = false;
	... // 省略get/set

	// 由此可见使用的是默认的校验器(当然还是Hibernate的)
	@Override
	public void afterPropertiesSet() 
		if (this.validator == null) 
			this.validator = Validation.buildDefaultValidatorFactory().getValidator();
		
	

	// 这个实现太简单了
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 
		if (!this.afterInitialization) 
			doValidate(bean);
		
		return bean;
	
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
		if (this.afterInitialization) 
			doValidate(bean);
		
		return bean;
	

	protected void doValidate(Object bean) 
		Assert.state(this.validator != null, "No Validator set");
		//如果是单例的,并且是被代理的代理对象,那就取出代理对象包裹的原单例对象
		Object objectToValidate = AopProxyUtils.getSingletonTarget(bean);
		//如果不是代理对象,或者不是单例对象
		if (objectToValidate == null) 
			objectToValidate = bean;
		
		Set<ConstraintViolation<Object>> result = this.validator.validate(objectToValidate);

		// 拼接错误消息最终抛出
		if (!result.isEmpty()) 
			StringBuilder sb = new StringBuilder("Bean state is invalid: ");
			for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) 
				ConstraintViolation<Object> violation = it.next();
				sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage());
				if (it.hasNext()) 
					sb.append("; ");
				
			
			throw new BeanInitializationException(sb.toString());
		
	


这个BeanValidationPostProcessor实现的功能确实非常的简单,无非就是对所有的Bean在初始化前/后进行校验。

我们若是对Spring Bean想做约束的话(比如对属性、构造器等等),使用它就非常的方便~

备注:BeanValidationPostProcessor默认可是没有被装配进容器的~


org.springframework.validation.Validator

应用程序特定对象的验证器,这是Spring自己的抽象,注意区别于javax.validation.Validator。这个接口完全脱离了任何基础设施或上下文,也就是说,它没有耦合到只验证Web层、数据访问层或任何层中的对象。它支持应用于程序内的任何层

// 注意:它可不是Spring3后才推出的  最初就有
public interface Validator 
	// 此clazz是否可以被validate
	boolean supports(Class<?> clazz);
	// 执行校验,错误消息放在Errors 装着
	// 可以参考ValidationUtils这个工具类,它能帮助你很多
	void validate(Object target, Errors errors);

该类的继承树如下:


SmartValidator

这个子接口它扩展增加了校验分组:hints。

// @since 3.1  这个出现得比较晚
public interface SmartValidator extends Validator 
	
	// 注意:这里的Hints最终都会被转化到JSR的分组里去~~
	// 所以这个可变参数,传接口Class对象即可~
	void validate(Object target, Errors errors, Object... validationHints);

	// @since 5.1  简单的说,这个方法子类请复写 否则不能使用
	default void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) 
		throw new IllegalArgumentException("Cannot validate individual value for " + targetType);
	



SpringValidatorAdapter:校验适配器(重要)

这个实现类Class是非常重要的,它是javax.validation.ValidatorSpringValidator的适配,通过它就可以对接到JSR的校验器来完成校验工作了~

在Spring5.0后,此实现类已完美支持到Bean Validation 2.0

// @since 3.0
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator 

	// 通用的三个约束注解都需要有的属性
	private static final Set<String> internalAnnotationAttributes = new HashSet<>(4);
	static 
		internalAnnotationAttributes.add("message");
		internalAnnotationAttributes.add("groups");
		internalAnnotationAttributes.add("payload");
	

	// 最终都是委托给它来完成校验的~~~
	@Nullable
	private javax.validation.Validator targetValidator;
    
    
	public SpringValidatorAdapter(javax.validation.Validator targetValidator) 
		Assert.notNull(targetValidator, "Target Validator must not be null");
		this.targetValidator = targetValidator;
	

	// 简单的说:默认支持校验所有的Bean类型~~~
	@Override
	public boolean supports(Class<?> clazz) 
		return (this.targetValidator != null);
	
	// processConstraintViolations做的事一句话解释:
	// 把ConstraintViolations错误消息,全都适配放在Errors(BindingResult)里面存储着
	@Override
	public void validate(Object target, Errors errors) 
		if (this.targetValidator != null) 
			processConstraintViolations(this.targetValidator.validate(target), errors);
		
	

	@Override
	public void validate(Object target, Errors errors, Object... validationHints) 
		if (this.targetValidator != null) 
			processConstraintViolations(this.targetValidator.validate(target,  asValidationGroups(validationHints)), errors);
		
	

	@SuppressWarnings("unchecked")
	@Override
	public void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) 
		if (this.targetValidator != null) 
			processConstraintViolations(this.targetValidator.validateValue(
					(Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
		
	

	// 把validationHints都转换为group (只识别Class类型)
	private Class<?>[] asValidationGroups(Object... validationHints) 
		Set<Class<?>> groups = new LinkedHashSet<>(4);
		for (Object hint : validationHints) 
			if (hint instanceof Class) 
				groups.add((Class<?>) hint);
			
		
		return ClassUtils.toClassArray(groups);
	

	// 关于Implementation of JSR-303 Validator interface  省略...

这个适配器它把所有的Spring接口的校验方法,最终都委托给了javax.validation.Validator,这样就可以完美的和JSR结合起来使用了,功能更加的强大~

虽然本类它是个Class实体类,但是一般来说不建议直接使用它


CustomValidatorBean

可配置(Custom)的Bean类,也同样的实现了双接口。它可以配置ValidatorFactory验证器工厂、MessageInterpolator插值器等…

public class CustomValidatorBean extends SpringValidatorAdapter implements Validator, InitializingBean 

	// javax.validation.ValidatorFactory
	@Nullable
	private ValidatorFactory validatorFactory;
	@Nullable
	private MessageInterpolator messageInterpolator;
	@Nullable
	private TraversableResolver traversableResolver;
	... // 省略所有set方法(木有get方法)

	// 默认设置~~~~初始化
	@Override
	public void afterPropertiesSet() 
		if (this.validatorFactory == null) 
			this.validatorFactory = Validation.buildDefaultValidatorFactory();
		

		// 这一句就是new ValidatorContextImpl( this )
		ValidatorContext validatorContext = this.validatorFactory.usingContext();

		// 插值器
		MessageInterpolator targetInterpolator = this.messageInterpolator;
		if (targetInterpolator == null) 
			targetInterpolator = this.validatorFactory.getMessageInterpolator();
		
		validatorContext.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator));
		if (this.traversableResolver != null) 
			validatorContext.traversableResolver(this.traversableResolver);
		

		// 把已经配置好的这个Validator设置进去~
		setTargetValidator(validatorContext.getValidator());
	

命名中就能可以看出,它是一个Bean,所以可以配合Spring容器一起使用。Spring内部虽然没有直接使用到它,但我们自己有需求的话自己可以使用它(其实更多的还是使用更强的子类)~


LocalValidatorFactoryBean

它和CustomValidatorBean平级,都是继承自SpringValidatorAdapter,但是它提供的能力更加的强大,比如Spring处理校验这块最重要的处理器MethodValidationPostProcessor就是依赖于它来给提供验证器~

它是Spring上下文中javax.validation的中心配置类。

// @since 3.0  这个类非常的丰富  实现了接口javax.validation.ValidatorFactory
// 实现了ApplicationContextAware拿到Spring上下文...
// 但其实,它的实际工作都是委托式,自己只提供了各式各样的配置~~~(主要是配置JSR)
public class LocalValidatorFactoryBean extends SpringValidatorAdapter implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean 
	... // 省略所有的配置属性
	... // 省略所有的get/set
	... // 省略afterPropertiesSet()进行的默认配置初始化  最终调用setTargetValidator(this.validatorFactory.getValidator());

	// 备注:还记得上文吗?上文的validator校验器是从上下文拿的,这里是从工厂拿的
	// 省略所有对ValidatorFactory接口的方法实现~

这个类是非常重要的,虽然它也不被Spring直接使用,但是它是基石。

备注:虽然命名后缀是FactoryBean,但它并不是org.springframework.beans.factory.FactoryBean这个接口的子类。
其实这是断句问题,正确断句方式是:Local ValidatorFactory Bean~


OptionalValidatorFactoryBean

@since 4.0.1提供的,它做的唯一一件事:让org.springframework.validation.Validator成为可选(即使没有初始化成功,也不会报错,相当于把异常吃了嘛~)

// @since 4.0.1
public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean 

	@Override
	public void afterPropertiesSet() 
		try 
			super.afterPropertiesSet();
		 catch (ValidationException ex) 
			LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
		
	


综上,若你想使用org.springframework.validation.SmartValidator来完成对Bean的校验,那就手动定义一个这样的Bean,然后自行调用API校验完成校验~

若你想这一切能面向注解编程,自动完成校验,那就听下文分解吧(也是最为关心,最为重要的内容)


SpringConstraintValidatorFactory

ConstraintValidatorFactory整个API前面有讲过,本类就是Spring对它的扩展,从而和Spring容器整合了

public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory 

	private final AutowireCapableBeanFactory beanFactory;
	public SpringConstraintValidatorFactory(AutowireCapableBeanFactory beanFactory) 
		Assert.notNull(beanFactory, "BeanFactory must not be null");
		this.beanFactory = beanFactory;
	

	// 注意:此处是直接调用了create方法,放进容器
	@Override
	public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) 
		return this.beanFactory.createBean(key);
	
	// Bean Validation 1.1 releaseInstance method
	public void releaseInstance(ConstraintValidator<?, ?> instance) 
		this.beanFactory.destroyBean(instance);
	



MessageSourceResourceBundleLocator

这个类也非常有意思,它扩展了Hibernate包的ResourceBundleLocator国际化,而使用

Spring自己的国际化资源:org.springframework.context.MessageSource

说明:ResourceBundleLocator是它Hibernate的一个SPI,Hibernate内部自己对它可是也有实现的哦~(Bean Validation内部大量的用到了SPI技术,有兴趣的可以了解)

public class MessageSourceResourceBundleLocator implements ResourceBundleLocator 

	private final MessageSource messageSource;
	public MessageSourceResourceBundleLocator(MessageSource messageSource) 
		Assert.notNull(messageSource, "MessageSource must not be null");
		this.messageSource = messageSource;
	

	@Override
	public ResourceBundle getResourceBundle(Locale locale) 
		return new MessageSourceResourceBundle(this.messageSource, locale);
	


关于MessageSourceResourceBundle它,就相对比较熟悉点了,它不是校验专用的,是Spring整体上用来处理国际化资源:MessageSource,java.util.ResourceBundle的帮助类

//@since 27.02.2003 java.util.ResourceBundle  它是JDK提供来读取国际化的属性配置文件的  是个抽象类
public class MessageSourceResourceBundle extends ResourceBundle 
	private final MessageSource messageSource;
	private final Locale locale;

	public MessageSourceResourceBundle(MessageSource source, Locale locale) 
		Assert.notNull(source, "MessageSource must not be null");
		this.messageSource = source;
		this.locale = locale;
	
	public MessageSourceResourceBundle(MessageSource source, Locale locale, ResourceBundle parent) 
		this(source, locale);
		setParent(parent);
	

	@Override
	@Nullable
	protected Object handleGetObject(String key) 
		try 
			return this.messageSource.getMessage(key, null, this.locale);
		 catch (NoSuchMessageException ex) 
			return null;
		
	

	// @since 1.6
	@Override
	public boolean containsKey(String key) 
		try 
			this.messageSource.getMessage(key, null, this.locale);
			return true;
		
		catch (NoSuchMessageException ex) 
			return false;
		
	
	@Override
	public Enumeration<String> getKeys() 
		throw new UnsupportedOperationException("MessageSourceResourceBundle does not support enumerating its keys");
	
	@Override
	public Locale getLocale() 
		return this.locale;
	


Spring环境下不仅可以使用Hibernate的国际化文件,也可以借助MessageSourceResourceBundleLocator搞自己的。


LocaleContextMessageInterpolator

它是个javax.validation.MessageInterpolator插值器,Spring把它和自己的LocaleContext结合起来了~

// @since 3.0
// org.springframework.context.i18n.LocaleContextHolder#getLocale()
public class LocaleContextMessageInterpolator implements MessageInterpolator 

	private final MessageInterpolator targetInterpolator;
	public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) 
		Assert.notNull(targetInterpolator, "Target MessageInterpolator must not be null");
		Bean Validation自定义容器验证篇----06

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

Spring功能介绍SpringMVC集成Java Bean Validation实现参数检验功

JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践

spring源码阅读 Bean加载之默认标签加载