JSR303、349 -Bean Validation 数据校验规范使用说明和验证流程源码分析

原创
2016/10/21 17:11
阅读数 8.6K

本文讲解基于jsr303和jsr349标准的 Bean Validation规范。通过以下部分讲解:

  1. 约束注解的定义
  2. 约束验证规则(约束验证器)
  3. 约束注解的声明
  4. 约束验证流程
  5. 在spring mvc中使用jsr349
  6. 使用工具类不使用Spring提供的功能
  7. Spring3.1支持方法级别验证
  8. dubbo中使用jsr303提供的验证
  9. 需要关注一下问题
  10. 参考网址

Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。数据校验是任何一个应用程序都会用到的功能,无论是显示层还是持久层. 通常,相同的校验逻辑会分散在各个层中, 这样,不仅浪费了时间还会导致重复代码(如下图). 为了避免重复, 开发人员经常会把这些校验逻辑直接写在领域模型里面, 但是这样又把领域模型代码和校验代码混杂在了一起, 而这些校验逻辑更应该是描述领域模型的元数据. 常规在各层的验证 JSR 303,349 - Bean Validation - 为实体验证定义了元数据模型和API. 默认的元数据模型是通过Annotations来描述的,但是也可以使用XML来重载或者扩展. Bean Validation API 并不局限于应用程序的某一层或者哪种编程模型, 例如,如下图所示, Bean Validation 可以被用在任何一层, 或者是像类似Swing的富客户端程序中. 使用验证框架后的模式 一个 constraint 通常由 annotation 和相应的 constraint validator 组成,它们是一对多的关系。也就是说可以有多个 constraint validator 对应一个 annotation。在运行时,Bean Validation 框架本身会根据被注释元素的类型来选择合适的 constraint validator 对数据进行验证。

1. 约束注解的定义

在 Java Bean 中,对某一方法、字段、属性或其组合形式等进行约束的注解,即为约束注解

@NotNull(message = "The id of employee can not be null") 
 private Integer id;

对于每一个约束注解,在实际使用前必须有相关定义。JSR303 规范默认提供了几种约束注解的定义,我们也可以扩展规范提供的 API,实现符合自身业务需求的约束注解(下文有示例)。 表 1. Bean Validation 中内置的 constraint

Constraint 详细信息
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
标准实现者如hibernate-validater提供的约束如下:
Constraint 详细信息
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

约束注解和普通的注解一样,一个典型的约束注解的定义应该至少包括如下内容:

  1. 约束注解应用的目标元素类型
  2. 验证时的组别属性
  3. 有效负载

1.1 约束注解应用的目标元素类型

约束注解应用的目标元素类型包括 METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER。METHOD 约束相关的 getter 方法;FIELD 约束相关的属性;TYPE 约束具体的 Java Bean;ANNOTATION_TYPE 用在组合约束中;该规范同样也支持对参数(PARAMETER)和构造器(CONSTRUCTOR)的约束。

@Target({ })   // 约束注解应用的目标元素类型
 @Retention()   // 约束注解应用的时机
 @Constraint(validatedBy ={})  // 与约束注解关联的验证器
 public @interface ConstraintName{ 
 String message() default " ";   // 约束注解验证时的输出消息
 Class<?>[] groups() default { };  // 约束注解在验证时所属的组别
 Class<? extends Payload>[] payload() default { }; // 约束注解的有效负载
 }

1.2 验证时的组别属性

默认验证组别为:javax.validation.groups.Default

1.3 有效负载

有效负载通常用来将一些元数据信息与该约束注解相关联,常用的一种情况是用负载表示验证结果的严重程度。

最后通过一段示例代码描述如何定义约束注解:

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MobliePhoneValidator.class)
@Documented
public @interface MobliePhone {
	
	String message() default "{com.xdja.constraints.MobliePhone.message}";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
//	String value();

}

约束必须是11号手机号码。

2. 约束验证规则(约束验证器)

约束验证规则的定义实现ConstraintValidator接口。 如下示例代码为定义一个简单的玉树验证规则:

public class MobliePhoneValidator implements ConstraintValidator<MobliePhone, String> {
	final String regExp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$";  

	public void initialize(MobliePhone constraintAnnotation) {//初始化
	}

	public boolean isValid(String object, ConstraintValidatorContext constraintContext) {//验证方法
		boolean isValid = false;
		if (object == null){
			return isValid;
		}
		if (Pattern.compile(regExp).matcher(object).matches()) {
			isValid = true;
		}
		if (isValid) {
			constraintContext.disableDefaultConstraintViolation();
			constraintContext.buildConstraintViolationWithTemplate("{com.xdja.constraints.MobliePhone.message}")
					.addConstraintViolation();
		}
		return isValid;
	}
}

虽然可以通过Bean Validation 提供的@Pattern(value) 注解约束但是这里只是提供一个示例参考。

3. 约束注解的声明

约束注解的声明及为使用定义好的和已有的约束注解、参考如下示例代码:

class User{
	
	@MobliePhone//约束注解的声明
	private String phone;

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}
}

4. 约束验证流程

在使用jsr303 or jsr 349时需要分别导入:

validation-api-1.0.0.GA.jar JSR-303规范API包
hibernate-validator-4.3.0.Final.jar Hibernate 参考实现
对JavaBean进行验证,如方法级别(方法参数/返回值)
validation-api-1.1.0.Final.jar
hibernate-validator-5.2.4.Final.jar
跨参数验证(比如密码和确认密码的验证)和支持在消息中使用EL表达式

介绍一下SPI技术

4.1 JavaSPI技术

一个服务(service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按spi标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。如有一个服务接口com.test.Service,其服务实现类为com.test.ChildService,那此时需要在META-INF/services中放置文件com.test.Service,其中的内容就为该实现类的全限定名com.test.ChildService,有多个服务实现,每一行写一个服务实现,#后面的内容为注释,并且该文件只能够是以UTF-8编码。 也可以参考如下网址:Java中的SPI(Service Provider Interface)介绍及示例 那么hibernate-validator是如何提供默认实现呢?如下图: 输入图片说明 由此可见是基于Java SPI技术的标准实现。

4.2源码分析hinbernate-validator对jsr303,349标准的实现

首先从获得Validator 对象开始:

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

源码分析javax.validation.Validation类的buildDefaultValidatorFactory方法为:

public static ValidatorFactory buildDefaultValidatorFactory() {
		return byDefaultProvider().configure().buildValidatorFactory();
	}

获得Configuration对象信息为创建ValidatorFactory做准备。继续查看javax.validation.Validation类的byDefaultProvider方法:

public static GenericBootstrap byDefaultProvider() {
		return new GenericBootstrapImpl();
	}

javax.validation.Validation.GenericBootstrapImpl 类是javax.validation.Validation类的 内部私有静态类,方法configure是核心方法:

public Configuration<?> configure() {
			ValidationProviderResolver resolver = this.resolver == null ?
					getDefaultValidationProviderResolver() ://注意此处调用
					this.resolver;

			List<ValidationProvider<?>> validationProviders;
			try {
				validationProviders = resolver.getValidationProviders();//获得ValidationProvider
			}
			// 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.size() == 0 ) {
				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 ValidationException( msg );
			}

			Configuration<?> config;
			try {//根据spi服务获取到的  Configuration 对象。该对象已经是Hibernate-validater对象实例了
				config = resolver.getValidationProviders().get( 0 ).createGenericConfiguration( this );
			}
			catch ( RuntimeException re ) {
				throw new ValidationException( "Unable to instantiate Configuration.", re );
			}

			return config;
		}
查看javax.validation.Validation.GenericBootstrapImpl 类另一个方法源码:
public ValidationProviderResolver getDefaultValidationProviderResolver() {
			if ( defaultResolver == null ) {
				defaultResolver = new DefaultValidationProviderResolver();
			}
			return defaultResolver;
		}

查看静态私有类javax.validation.Validation.DefaultValidationProviderResolver源码为:

private static class DefaultValidationProviderResolver implements ValidationProviderResolver {
		public List<ValidationProvider<?>> getValidationProviders() {
			// class loading and ServiceLoader methods should happen in a PrivilegedAction
			return GetValidationProviderListAction.getValidationProviderList();
		}
	}

获得ValidationProvider对象。 查看静态私有类javax.validation.Validation.GetValidationProviderListAction中的私有方法loadProviders:

private List<ValidationProvider<?>> loadProviders(ClassLoader classloader) {
			ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );//调用该方法已经获取到 services中spi接口实现类
			Iterator<ValidationProvider> providerIterator = loader.iterator();
			List<ValidationProvider<?>> validationProviderList = new ArrayList<ValidationProvider<?>>();
			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;
		}

以上方法为使用Java SPI发现hibernate-validater的实现方式。

ValidatorFactory对象的创建使用org.hibernate.validator.internal.engine.ConfigurationImpl类的buildValidatorFactory()方法、源码如下:

public final ValidatorFactory buildValidatorFactory() {
		parseValidationXml();//解析xml配置
		ValidatorFactory factory = null;
		try {
			if ( isSpecificProvider() ) {
				factory = validationBootstrapParameters.getProvider().buildValidatorFactory( this );
			}
			else {
				final Class<? extends ValidationProvider<?>> providerClass = validationBootstrapParameters.getProviderClass();
				if ( providerClass != null ) {
					for ( ValidationProvider<?> provider : providerResolver.getValidationProviders() ) {
						if ( providerClass.isAssignableFrom( provider.getClass() ) ) {
							factory = provider.buildValidatorFactory( this );
							break;
						}
					}
					if ( factory == null ) {
						throw log.getUnableToFindProviderException( providerClass );
					}
				}
				else {
					List<ValidationProvider<?>> providers = providerResolver.getValidationProviders();
					assert providers.size() != 0; // I run therefore I am
					factory = providers.get( 0 ).buildValidatorFactory( this );
				}
			}
		}
		finally {
			// close all input streams opened by this configuration
			for ( InputStream in : configurationStreams ) {
				try {
					in.close();
				}
				catch ( IOException io ) {
					log.unableToCloseInputStream();
				}
			}
		}

		return factory;
	}

通过ValidatorFactory 就可以获得Validator对象了。

4.3 其他特性

有些时候,在用户的应用中需要一些更复杂的 constraint。Bean Validation 提供扩展 constraint 的机制。可以通过两种方法去实现,一种是组合现有的 constraint 来生成一个更复杂的 constraint,另外一种是开发一个全新的 constraint。

4.3.1 组合现有的constraint来生成一个更复杂的constraint

通过如下代码示例:

// @Max 和 @Min 都是内置的 constraint 
 @Max(10000) 
 @Min(8000) 
 @Constraint(validatedBy = {}) 
 @Documented 
 @Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD }) 
 @Retention(RetentionPolicy.RUNTIME) 
 public @interface Price { 
 String message() default "错误的价格"; 
 Class<?>[] groups() default {}; 
 Class<? extends Payload>[] payload() default {}; 
 }

使用注释约束:

@Price
private Integer price;

校验:

User u = new User();
		u.setPhone("13100000000");
		u.setPrice(1);
		ValidationResult vr = ValidationUtils.validateEntity(u);//验证
		System.out.println(vr.isHasErrors());
		if (vr.isHasErrors()) {
			for (String key : vr.getErrorMsg().keySet()) {
				System.out.println("key:"+key+",value:"+vr.getErrorMsg().get(key));
			}
		}

输出内容为: key:price,value:最小不能小于8000

4.3.2 开发一个全新的 constraint

上述中已经有定义。

5. 在spring mvc中使用jsr349

使用方式如下代码:

伪代码如下:
public class PersonAppTimeRank{
@NotEmpty(message="{appId.null}")
	private String appId;//应用ID【必填】
	@NotEmpty(message="{personId.null}")
	private String personId;//用户ID【必填】
}	

控制层方法定义:

public ResponseBean personAppCountRankingList0(
						@Valid PersonAppTimeRank appTimeRank,
						BindingResult result){
  //验证后的错误信息都在 result中
 List<ObjectError> listOe = result.getAllErrors();
包含所有未验证通过的对象集合信息
}

注意xml文件配置如下:

<mvc:annotation-driven
		validator="validator"
		/>
<!-- 以下 validator ConversionService 在使用 mvc:annotation-driven 会 自动注册 -->
	<bean id="validator"
		class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
		<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
		<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
		<property name="validationMessageSource" ref="messageSource" />
	</bean>

	<!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
	<bean id="messageSource"
		class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找 -->
				<value>classpath:messages/messages</value>
				<value>classpath:org/hibernate/validator/ValidationMessages</value>
			</list>
		</property>
		<property name="useCodeAsDefaultMessage" value="true" />
		<property name="defaultEncoding" value="UTF-8" />
		<property name="cacheSeconds" value="60" />
	</bean>

查看org.springframework.validation.beanvalidation.LocalValidatorFactoryBean工厂bean类的方法:

@Override
	public void afterPropertiesSet() {
		@SuppressWarnings({"rawtypes", "unchecked"})//和上边hibernate validater雷同
		Configuration<?> configuration = (this.providerClass != null ?
				Validation.byProvider(this.providerClass).configure() :
				Validation.byDefaultProvider().configure());

		// Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
		if (this.applicationContext != null) {
			try {
				Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class);
				ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
			}
			catch (NoSuchMethodException ex) {
				// Ignore - no Hibernate Validator 5.2+ or similar provider
			}
		}

		MessageInterpolator targetInterpolator = this.messageInterpolator;
		if (targetInterpolator == null) {
			targetInterpolator = configuration.getDefaultMessageInterpolator();
		}
		configuration.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator));

		if (this.traversableResolver != null) {
			configuration.traversableResolver(this.traversableResolver);
		}

		ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
		if (targetConstraintValidatorFactory == null && this.applicationContext != null) {
			targetConstraintValidatorFactory =
					new SpringConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory());
		}
		if (targetConstraintValidatorFactory != null) {
			configuration.constraintValidatorFactory(targetConstraintValidatorFactory);
		}

		if (this.parameterNameDiscoverer != null) {
			configureParameterNameProviderIfPossible(configuration);
		}

		if (this.mappingLocations != null) {
			for (Resource location : this.mappingLocations) {
				try {
					configuration.addMapping(location.getInputStream());
				}
				catch (IOException ex) {
					throw new IllegalStateException("Cannot read mapping resource: " + location);
				}
			}
		}

		for (Map.Entry<String, String> entry : this.validationPropertyMap.entrySet()) {
			configuration.addProperty(entry.getKey(), entry.getValue());
		}

		// Allow for custom post-processing before we actually build the ValidatorFactory.
		postProcessConfiguration(configuration);

		this.validatorFactory = configuration.buildValidatorFactory();
		setTargetValidator(this.validatorFactory.getValidator());
	}

6. 使用工具类不使用Spring提供的功能

也可以通过定义工具类直接显示的调用、其中工具类定义如下:

@Component
public class ValidationUtilBean {
//在setter方法中  注入
private static Validator validator /*= Validation.buildDefaultValidatorFactory().getValidator()*/;

	/**
	 * 校验对象
	 * @param obj
	 * @return
	 */
	public static <T> ValidationResult validateEntity(T obj) {
		ValidationResult result = new ValidationResult();
		Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
		if (set != null && set.size() > 0) {
			result.setHasErrors(true);
			Map<String, String> errorMsg = new HashMap<String, String>();
			for (ConstraintViolation<T> cv : set) {
				errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
			}
			result.setErrorMsg(errorMsg);
		}
		return result;
	}

	/**
	 * 校验属性
	 * 
	 * @param obj
	 * @param propertyName
	 * @return
	 */
	public static <T> ValidationResult validateProperty(T obj, String propertyName) {
		ValidationResult result = new ValidationResult();
		Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
		if (set != null && set.size() > 0) {
			result.setHasErrors(true);
			Map<String, String> errorMsg = new HashMap<String, String>();
			for (ConstraintViolation<T> cv : set) {
				errorMsg.put(propertyName, cv.getMessage());
			}
			result.setErrorMsg(errorMsg);
		}
		return result;
	}

	public  Validator getValidator() {
		return validator;
	}

	@Autowired
	public  void setValidator(Validator validator) {
		ValidationUtilBean.validator = validator;
	}
}

注意 @Autowired public void setValidator(Validator validator) {} 需要一个配置好的validator bean。可以参考 6 中spring的定义。

在控制层显示使用代码调用如下:

ValidationResult result = ValidationUtils.validateEntity(appTimeRank);

ValidationUtils类就是上边定义的工具类、可以验证实体、实体中属性都可以.

7. Spring3.1支持方法级别验证

没有MethodValidationPostProcessor之前我们可能这样验证:

public UserModel get(Integer uuid) {
    //前置条件
    Assert.notNull(uuid);
    Assert.isTrue(uuid > 0, "uuid must lt 0");

    //获取 User Model
    UserModel user = new UserModel(); //此处应该从数据库获取

    //后置条件
    Assert.notNull(user);
    return user;
}

有了MethodValidationPostProcessor之后我们可以这样验证:

public @NotNull UserModel get2(@NotNull @Size(min = 1) Integer uuid) {
    //获取 User Model
    UserModel user = new UserModel(); //此处应该从数据库获取
    return user;
}
  • 前置条件的验证:在方法的参数上通过Bean Validation注解进行实施;
  • 后置条件的验证:直接在返回值上通过Bean Validation注解进行实施。

7.1 示例代码:

示例:

  1. Service类定义
@Validated      //① 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持
public class UserService {
    public @NotNull UserModel get2(@NotNull @Min(value = 1) Integer uuid) { //②声明前置条件/后置条件
        //获取 User Model
        UserModel user = new UserModel(); //此处应该从数据库获取
        if(uuid > 100) {//方便后置添加的判断(此处假设传入的uuid>100 则返回null)
            return null;
        }
        return user;
    }
}
  1. 开启Spring3.1对方法级别验证支持
<!--注册方法验证的后处理器-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

如果出现异常就抛出ConstraintViolationException,核心代码类org.springframework.validation.beanvalidation.MethodValidationInterceptor实现上述功能:

public Object invoke(MethodInvocation invocation) throws Throwable {
		Class<?>[] groups = determineValidationGroups(invocation);//

		if (forExecutablesMethod != null) {//===============  Bean Validation 1.1 
			// Standard Bean Validation 1.1 API
			Object execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator);
			Method methodToValidate = invocation.getMethod();
			Set<ConstraintViolation<?>> result;

			try {//方法参数校验
				result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
						execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
			}
			catch (IllegalArgumentException ex) {
				// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
				// Let's try to find the bridged method on the implementation class...
				methodToValidate = BridgeMethodResolver.findBridgedMethod(
						ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
				result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
						execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
			}//如果有直接抛出异常
			if (!result.isEmpty()) {
				throw new ConstraintViolationException(result);
			}
//执行方法调用 返回结果集
			Object returnValue = invocation.proceed();
     //方法返回结果校验开始
			result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateReturnValueMethod,
					execVal, invocation.getThis(), methodToValidate, returnValue, groups);
			if (!result.isEmpty()) {//如果有直接抛出异常
				throw new ConstraintViolationException(result);
			}

			return returnValue;
		}

		else {//================ 默认使用  hibernate Validator 303

			// Hibernate Validator 4.3's native API
			return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups);
		}
	}

方法级别验证会出现一下问题: 代码如下:

@Validated
@Transactional
public interface UserService {
	UserModel login(@NotBlank String account, @NotEmpty String password);
}

问题描述: 在接口应用MethodValidationPostProcessor和Spring注解式事务后发现: 验证参数之前就先开启了事务(获得了DB连接),应该先验证输入,验证失败后直接抛出异常,验证成功再开启事务,这样对业务层的性能才更好。

  • 第一种解决方式:

可以修改它们的order来完成;不能使用tx:ann…… 注册 ,手工注册相应的bean指定order即可.如果使用注解bean就懵逼了.还好Spring 4.2 利用@Order控制配置类的加载顺序.可以参考如下:http://wiselyman.iteye.com/blog/2217192

  • 第二种解决方式:通过配置bean解决
<!-- 这个标签上可以写order,默认是Integer.MAX_VALUE -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 其他配置项配置 -->
<bean id="validator" class="com.gmail.dohongdayi.ssh.common.validation.ValidatorFactoryBean">
	<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
	<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties
	<property name="validationMessageSource" ref="messageSource" /> -->
</bean>
<!-- 定义切入点 -->
<bean id="validationPointcut" class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
	<constructor-arg index="0" value="org.springframework.validation.annotation.Validated" />
	<constructor-arg index="1" value="true" />
</bean>
<!--  定义通知-->
<bean id="validationAdvice" class="org.springframework.validation.beanvalidation.MethodValidationInterceptor">
	<constructor-arg index="0" ref="validator" />
</bean>

<!-- 替代MethodValidationPostProcessor,让验证切面的advisor加入到Spring AOP的AspectJAwareAdvisorAutoProxyCreator的advised -->
<aop:config>
	<!-- 通过order指定验证优先于事务(100 < Integer.MAX_VALUE) -->
	<aop:advisor pointcut-ref="validationPointcut" advice-ref="validationAdvice" order="100" />
</aop:config>

8. dubbo中使用jsr303提供的验证

在客户端验证参数:

<dubbo:reference id="validationService" interface="com.alibaba.dubbo.examples.validation.api.ValidationService" validation="true" />

在服务器端验证参数:

<dubbo:service interface="com.alibaba.dubbo.examples.validation.api.ValidationService" ref="validationService" validation="true" />

参考以下网址: double中使用jsr303 出现的异常为:RpcException

 ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException
            Set<ConstraintViolation<?>> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
            System.out.println(violations);
就获得了原生的异常信息。

从源代码分析实现原始和支持的功能,查看类com.alibaba.dubbo.validation.filter.ValidationFilter的源码核心方法invoke:

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (validation != null && ! invocation.getMethodName().startsWith("$") 
                && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.VALIDATION_KEY))) {//判断你的  <dubbo:service  <dubbo:reference 是否配置有  validation配置项如果有进行 jsr303验证、如果没有不验证
            try {// validation 稍后介绍   获得 validator对象  
                Validator validator = validation.getValidator(invoker.getUrl());
                if (validator != null) {// 如果validator 对象部位空  开始进行验证。验证的核心代码就是  validation要介绍的。爬出的RpcException 直接抛出  抛出的其它异常包装为 RpcException异常 就是开头介绍的:验证出现异常获得异常的形式
                    validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
                }
            } catch (RpcException e) {
                throw e;
            } catch (Throwable t) {
                throw new RpcException(t.getMessage(), t);
            }
        }
        return invoker.invoke(invocation);
    }

查看如下链接了解Validation的扩展点:http://dubbo.io/Developer+Guide-zh.htm#DeveloperGuide-zh-Validation现在dubbox框架支持jsr303标准、后期需要可以继续扩展支持349标准.重点介绍com.alibaba.dubbo.validation.support.jvalidation.JValidation对象。 该类的源码如下:

public class JValidation extends AbstractValidation {
    @Override
    protected Validator createValidator(URL url) {
        return new JValidator(url);//使用 JValidator创建Validator 
    }}

查看com.alibaba.dubbo.validation.support.jvalidation.JValidator如下图: 输入图片说明 发现只提供了一个公共方法提供校验、因此不支持方法返回值校验等高级特性

9. 需要关注一下问题

  1. 错误消息的顺序问题
  2. 动态显示错误提醒
  3. 部分验证部分不验证

9.1 错误消息的顺序问题

通过@GroupSequence指定验证顺序:

@GroupSequence({First.class, Second.class, User.class})

先验证First分组,如果有错误立即返回而不会验证Second分组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

9.2 动态显示错误提醒

首先确定引入EL jar包且版本正确。然后使用如: user.name.length.illegal=用户名[${validatedValue}]长度必须在5到20之间

9.3 部分验证部分不验证

先使用分组@Validated注解 即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})。

10. 参考网址

展开阅读全文
打赏
0
5 收藏
分享
加载中
有个问题 方法验证的方式 验证失败 返回的都是相同的异常。。只能通过message区分 错误么。。
2017/03/26 16:45
回复
举报
更多评论
打赏
1 评论
5 收藏
0
分享
返回顶部
顶部