This document is the specification of the Java API for JavaBean validation in Java EE and Java SE. The technical objective of this work is to provide a class level constraint declaration and validation facility for the Java application developer, as well as a constraint metadata repository and query API.
目标:
Validating data is a common task that occurs throughout an application, from the presentation layer to the persistence layer. Often the same validation logic is implemented in each layer, proving to be time consuming and error-prone. To avoid duplication of these validations in each layer, developers often bundle validation logic directly into the domain model, cluttering domain classes with validation code that is, in fact, metadata about the class itself.
This JSR defines a metadata model and API for JavaBean validation. The default metadata source is annotations, with the ability to override and extend the meta-data through the use of XML validation descriptors.
The validation API developed by this JSR is not intended for use in any one tier or programming model. It is specifically not tied to either the web tier or the persistence tier, and is 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. Ease of use and flexibility have influenced the design of this specification.
/** * The chosen provider is defined as followed: * <ul> * <li>if the XML configuration defines a provider, this provider is used</li> * <li>if the XML configuration does not define a provider or if no XML * configuration is present the first provider returned by the * {@link ValidationProviderResolver} instance is used.</li> * </ul> **/
优先使用xml的配置:
1 2 3 4 5 6 7 8 9
//javax.validation.Configuration /** * <p/> * By default, the configuration information is retrieved from * {@code META-INF/validation.xml}. * It is possible to override the configuration retrieved from the XML file * by using one or more of the {@code Configuration} methods. * <p/> **/
assertEquals(1, constraintViolations.size()); assertEquals("must be greater than or equal to 2", constraintViolations.iterator() .next() .getMessage()); }
@Test publicvoidcarIsValid(){ Car car = new Car("Morris", "DD-AB-123", 2);
@Test publicvoidtestValidatePerson(){ final Car car = new Car("Morris", "豫D-AAAAA", 2, new Person(null)); final Set<ConstraintViolation<Car>> violations = validator.validate(car); for (ConstraintViolation<Car> violation: violations) { System.out.println(violation.getPropertyPath() + " -> " + violation.getMessage()); } assertNotNull(violations); }
/** * Finds {@link ValidationProvider} according to the default {@link ValidationProviderResolver} defined in the * Bean Validation specification. This implementation first uses thread's context classloader to locate providers. * If no suitable provider is found using the aforementioned class loader, it uses current class loader. * If it still does not find any suitable provider, it tries to locate the built-in provider using the current * class loader. * * @author Emmanuel Bernard * @author Hardy Ferentschik */ privatestaticclassDefaultValidationProviderResolverimplementsValidationProviderResolver{ public List<ValidationProvider<?>> getValidationProviders() { // class loading and ServiceLoader methods should happen in a PrivilegedAction return GetValidationProviderListAction.getValidationProviderList(); } }
默认的策略是:
先从thread’s context class loader 中加载SPI接口ValidationProvider对应的实现,
// javax.validation.Validation.GetValidationProviderListAction public List<ValidationProvider<?>> run() { // Option #1: try first context class loader //从contextClassLoader中加载 ClassLoader classloader = Thread.currentThread().getContextClassLoader(); List<ValidationProvider<?>> cachedContextClassLoaderProviderList = getCachedValidationProviders( classloader ); if ( cachedContextClassLoaderProviderList != null ) { // if already processed return the cached provider list return cachedContextClassLoaderProviderList; } List<ValidationProvider<?>> validationProviderList = loadProviders( classloader );
// Option #2: if we cannot find any service files with the context class loader use the current class loader // // // // if ( validationProviderList.isEmpty() ) { // 从当前class的加载器加载 classloader = DefaultValidationProviderResolver.class.getClassLoader(); List<ValidationProvider<?>> cachedCurrentClassLoaderProviderList = getCachedValidationProviders( classloader ); if ( cachedCurrentClassLoaderProviderList != null ) { // if already processed return the cached provider list return cachedCurrentClassLoaderProviderList; } validationProviderList = loadProviders( classloader ); }
// cache the detected providers against the classloader in which they were found cacheValidationProviders( classloader, validationProviderList );
return validationProviderList; }
至于loadProviders就是加载SPI的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
private List<ValidationProvider<?>> loadProviders(ClassLoader classloader) { ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader ); 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; }
// process first single groups. For these we can optimise object traversal by first running all validations on the current bean // before traversing the object. // 单个组的 Iterator<Group> groupIterator = validationOrder.getGroupIterator(); while ( groupIterator.hasNext() ) { Group group = groupIterator.next(); valueContext.setCurrentGroup( group.getDefiningClass() ); validateConstraintsForCurrentGroup( context, valueContext ); if ( shouldFailFast( context ) ) { return context.getFailingConstraints(); } } groupIterator = validationOrder.getGroupIterator(); while ( groupIterator.hasNext() ) { Group group = groupIterator.next(); valueContext.setCurrentGroup( group.getDefiningClass() ); // 校验级联的 validateCascadedConstraints( context, valueContext ); if ( shouldFailFast( context ) ) { return context.getFailingConstraints(); } }
// now we process sequences. For sequences I have to traverse the object graph since I have to stop processing when an error occurs. // 多个组的 @GroupSequence({RentalCar.class, CarChecks.class, DriverChecks.class}) Iterator<Sequence> sequenceIterator = validationOrder.getSequenceIterator(); while ( sequenceIterator.hasNext() ) { Sequence sequence = sequenceIterator.next(); for ( Group group : sequence.getComposingGroups() ) { int numberOfViolations = context.getFailingConstraints().size(); valueContext.setCurrentGroup( group.getDefiningClass() );
/** * Checks that the character sequence is not {@code null} nor empty after removing any leading or trailing * whitespace. * * @param charSequence the character sequence to validate * @param constraintValidatorContext context in which the constraint is evaluated * @return returns {@code true} if the string is not {@code null} and the length of the trimmed * {@code charSequence} is strictly superior to 0, {@code false} otherwise */ @Override publicbooleanisValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext){ if ( charSequence == null ) { returnfalse; }
By default use of @EnableWebMvc or <mvc:annotation-driven> automatically registers Bean Validation support in Spring MVC through the LocalValidatorFactoryBean when a Bean Validation provider such as Hibernate Validator is detected on the classpath.
/** * Throws MethodArgumentNotValidException if validation fails. * @throws HttpMessageNotReadableException if {@link RequestBody#required()} * is {@code true} and there is no body content or if there is no suitable * converter to read the content with. */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throws Exception {
// org.springframework.validation.beanvalidation.BeanValidationPostProcessor /** * Simple {@link BeanPostProcessor} that checks JSR-303 constraint annotations * in Spring-managed beans, throwing an initialization exception in case of * constraint violations right before calling the bean's init method (if any). * * @author Juergen Hoeller * @since 3.0 */ publicclassBeanValidationPostProcessorimplementsBeanPostProcessor, InitializingBean{ @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; } }
/** * Perform validation of the given bean. * @param bean the bean instance to validate * @see javax.validation.Validator#validate */ protectedvoiddoValidate(Object bean){ Set<ConstraintViolation<Object>> result = this.validator.validate(bean); 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("; "); } } thrownew BeanInitializationException(sb.toString()); } }
1 2
> Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: age - 最小不能小于10; id - 不能为null >