JSR30三、349 -Bean Validation 數據校驗規範使用說明和驗證流程源碼分析

本文講解基於jsr303和jsr349標準的 Bean Validation規範。經過如下部分講解:java

  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 對數據進行驗證。git

1. 約束註解的定義

在 Java Bean 中,對某一方法、字段、屬性或其組合形式等進行約束的註解,即爲約束註解web

@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 被註釋的元素必須在合適的範圍內

約束註解和普通的註解同樣,一個典型的約束註解的定義應該至少包括以下內容:spring

  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 有效負載

有效負載一般用來將一些元數據信息與該約束註解相關聯,經常使用的一種狀況是用負載表示驗證結果的嚴重程度。api

最後經過一段示例代碼描述如何定義約束註解:服務器

@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號手機號碼。mvc

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. 參考網址

相關文章
相關標籤/搜索