added MethodValidationInterceptor/PostProcessor for Hibernate Validator 4.2 based method validation; renamed Spring's variant of @Valid to @Validated

This commit is contained in:
Juergen Hoeller
2011-12-09 18:09:14 +00:00
parent 2dba480d9d
commit e648245eb3
11 changed files with 455 additions and 68 deletions

View File

@@ -1,56 +1,63 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Extended variant of JSR-303's {@link javax.validation.Valid},
* supporting the specification of validation groups. Designed for
* convenient use with Spring's JSR-303 support but not JSR-303 specific.
*
* <p>Can be used e.g. with Spring MVC handler methods arguments.
* Supported through {@link org.springframework.validation.SmartValidator}'s
* validation hint concept, with validation group classes acting as hint objects.
*
* @author Juergen Hoeller
* @since 3.1
* @see javax.validation.Validator#validate(Object, Class[])
* @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...)
* @see org.springframework.validation.beanvalidation.SpringValidatorAdapter
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
/**
* Specify one or more validation groups to apply to the validation step
* kicked off by this annotation.
* <p>JSR-303 defines validation groups as custom annotations which an application declares
* for the sole purpose of using them as type-safe group arguments, as implemented in
* {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may
* support class arguments in other ways as well.
*/
Class[] value() default {};
}
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Variant of JSR-303's {@link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
* <p>Can be used e.g. with Spring MVC handler methods arguments.
* Supported through {@link org.springframework.validation.SmartValidator}'s
* validation hint concept, with validation group classes acting as hint objects.
*
* <p>Can also be used with method level validation, indicating that a specific
* class is supposed to be validated at the method level (acting as a pointcut
* for the corresponding validation interceptor), but also optionally specifying
* the validation groups for method-level validation in the annotated class.
* Can also be used as a meta-annotation on a custom stereotype annotation.
*
* @author Juergen Hoeller
* @since 3.1
* @see javax.validation.Validator#validate(Object, Class[])
* @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...)
* @see org.springframework.validation.beanvalidation.SpringValidatorAdapter
* @see org.springframework.validation.beanvalidation.MethodValidationPostProcessor
*/
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
/**
* Specify one or more validation groups to apply to the validation step
* kicked off by this annotation.
* <p>JSR-303 defines validation groups as custom annotations which an application declares
* for the sole purpose of using them as type-safe group arguments, as implemented in
* {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may
* support class arguments in other ways as well.
*/
Class[] value() default {};
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.beanvalidation;
import java.util.Set;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.method.MethodConstraintViolation;
import org.hibernate.validator.method.MethodConstraintViolationException;
import org.hibernate.validator.method.MethodValidator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.annotation.Validated;
/**
* An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
* <p>Applicable methods have JSR-303 constraint annotations on their parameters
* and/or on their return value (in the latter case specified at the method level,
* typically as inline annotation).
*
* <p>E.g.: <code>public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)</code>
*
* <p>Validation groups can be specified through Spring's {@link Validated} annotation
* at the type level of the containing target class, applying to all public service methods
* of that class. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher.
* In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider
* and automatically use the standard method validation support there (once available).
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationPostProcessor
* @see org.hibernate.validator.method.MethodValidator
*/
public class MethodValidationInterceptor implements MethodInterceptor {
private final MethodValidator validator;
/**
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
this(Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory.
* @param validatorFactory the JSR-303 ValidatorFactory to use
*/
public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
this(validatorFactory.getValidator());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 Validator.
* @param validatorFactory the JSR-303 Validator to use
*/
public MethodValidationInterceptor(Validator validator) {
this.validator = validator.unwrap(MethodValidator.class);
}
public Object invoke(MethodInvocation invocation) throws Throwable {
Class[] groups = determineValidationGroups(invocation);
Set<MethodConstraintViolation<Object>> result = this.validator.validateAllParameters(
invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups);
if (!result.isEmpty()) {
throw new MethodConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
result = this.validator.validateReturnValue(
invocation.getThis(), invocation.getMethod(), returnValue, groups);
if (!result.isEmpty()) {
throw new MethodConstraintViolationException(result);
}
return returnValue;
}
/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the containing target class of the method.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class[] determineValidationGroups(MethodInvocation invocation) {
Validated valid = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
return (valid != null ? valid.value() : new Class[0]);
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.beanvalidation;
import java.lang.annotation.Annotation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.annotation.Validated;
/**
* A convenient {@link BeanPostProcessor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
* <p>Applicable methods have JSR-303 constraint annotations on their parameters
* and/or on their return value (in the latter case specified at the method level,
* typically as inline annotation).
*
* <p>E.g.: <code>public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)</code>
*
* <p>Target classes with such annotated methods need to be annotated with Spring's
* {@link Validated} annotation at the type level, for their methods to be searched for
* inline constraint annotations. Validation groups can be specified through {@link Validated}
* as well. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher.
* In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider
* and automatically use the standard method validation support there (once available).
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationInterceptor
* @see org.hibernate.validator.method.MethodValidator
*/
public class MethodValidationPostProcessor extends ProxyConfig
implements BeanPostProcessor, BeanClassLoaderAware, Ordered, InitializingBean {
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
private Validator validator;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Advisor advisor;
/**
* Set the 'validated' annotation type.
* The default validated annotation type is the {@link Validated} annotation.
* <p>This setter property exists so that developers can provide their own
* (non-Spring-specific) annotation type to indicate that a class is supposed
* to be validated in the sense of applying method validation.
* @param validatedAnnotationType the desired annotation type
*/
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
this.validatedAnnotationType = validatedAnnotationType;
}
/**
* Set the JSR-303 Validator to delegate to for validating methods.
* <p>Default is the default ValidatorFactory's default Validator.
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Set the JSR-303 ValidatorFactory to delegate to for validating methods,
* using its default Validator.
* <p>Default is the default ValidatorFactory's default Validator.
* @see javax.validation.ValidatorFactory#getValidator()
*/
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validator = validatorFactory.getValidator();
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public int getOrder() {
// This should run after all other post-processors, so that it can just add
// an advisor to existing proxies rather than double-proxy.
return LOWEST_PRECEDENCE;
}
public void afterPropertiesSet() {
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
Advice advice = (this.validator != null ? new MethodValidationInterceptor(this.validator) :
new MethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (AopUtils.canApply(this.advisor, targetClass)) {
if (bean instanceof Advised) {
((Advised) bean).addAdvisor(this.advisor);
return bean;
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(this.beanClassLoader);
}
}
else {
// This is not a repository.
return bean;
}
}
}

View File

@@ -89,7 +89,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
processConstraintViolations(this.targetValidator.validate(target), errors);
}
public void validate(Object target, Errors errors, Object[] validationHints) {
public void validate(Object target, Errors errors, Object... validationHints) {
Set<Class> groups = new LinkedHashSet<Class>();
if (validationHints != null) {
for (Object hint : validationHints) {